2014-02-26 29 views
1

我正在寻找最有效的方法来在IEnumerable<T> B的每一行之前对IEnumerable<T> A中的所有行进行网格划分。Meshing IEnumerable <T>一起列表

例如:

A = {A,B}

B = {1,2,3}

啮合后:

B = {A,1 ,B,2,A,3}

+0

所以你W¯¯当一个较短的列表在一个较长的列表之前完成时,蚂蚁将环绕到开始? [MoreLINQ](https://code.google.com/p/morelinq/source/browse/MoreLinq/Interleave.cs)有一个'Interleave'操作,几乎可以实现这一点。添加一个新的'ImbalancedInterleaveStrategy'不会太困难,该新的'ImbalancedInterleaveStrategy'指出要再次回到较短的列表的开始处。 –

回答

7

有没有简单的解决方案,但这样的事情可能会起作用。模数运算符在这里非常重要,用少量项目重复列表中的结果。

public static List<object> Mesh<T1, T2>(IEnumerable<T1> s1, IEnumerable<T2> s2) 
{ 
    T1[] array1 = s1.ToArray(); 
    T2[] array2 = s2.ToArray(); 
    int length1 = array1.Length; 
    int length2 = array2.Length; 
    int maxLength = Math.Max(length1, length2); 
    List<object> result = new List<object>(); 
    for (int i = 0; i < maxLength; i++) 
    { 
      result.Add(array1[i % length1]); 
      result.Add(array2[i % length2]); 
    } 
    return result. 
} 
+0

这实际上是唯一的正确的解决方案,但你可以把'Math.Max'放在循环之外。 –

+0

@SebastianStehle非常好的解决方案。也许我应该使它作为扩展,只有一种类型T –

+0

谢谢,这工作 – COBOL

0

试试这个,我的第一个猜测

for(int countA = 0, countB = 0; countA < A.Length; countA ++, countB++) 
{ 
    if(countB >= B.Length) 
    { 
     countB = 0; 
    } 

    newList.Add(A[countA]); 
    newList.Add(B[countB]); 
} 
+0

这并不完全,在OP中A列表更短,所以这不会生成所需的输出。 –

+0

但模式是正确的,我不打算做所有的工作为OP –

+0

我不能访问'IEnumerable ' – COBOL

2

该解决方案直接使用IEnumerable参数而无需初始化它们,并且还返回IEnumerable。

public IEnumerable<object> Mesh<S,T>(IEnumerable<S> items1, IEnumerable<T> items2){ 
    bool items1Empty; 
    bool items2Empty; 
    bool items1Finished = items1Empty = ValidateParameter(items1, "items1"); 
    bool items2Finished = items2Empty = ValidateParameter(items2, "items2"); 

    using(var items1Enumerator = items1.GetEnumerator()){ 
     using(var items2Enumerator = items2.GetEnumerator()){ 
      while(true){ 
       MoveNext(items1Enumerator, ref items1Finished); 
       MoveNext(items2Enumerator, ref items2Finished); 

       if(items1Finished && items2Finished) 
        break; 

       if(!items1Empty) 
        yield return items1Enumerator.Current; 

       if(!items2Empty) 
        yield return items2Enumerator.Current; 
      } 
     } 
    } 
} 

private bool ValidateParameter<T>(IEnumerable<T> parameter, string parameterName){ 
    if(parameter == null) 
     throw new ArgumentNullException(parameterName); 
    return !parameter.Any(); 
} 

private void MoveNext(IEnumerator enumerator, ref bool finished){ 
    if(!enumerator.MoveNext()){ 
     enumerator.Reset(); 
     enumerator.MoveNext(); 
     finished = true; 
    } 
} 
+1

像这样的解决方案就是答案,因为问题是“最高效的”。任何包含ToArray/ToList调用的解决方案都可能不符合要求。 –

+0

我清理了一些代码并纠正了可能发生的一些错误行为。 – dwonisch

0

我也想参加一个派对!因为我喜欢LINQ非常多,让我们来看看下面的LINQ溶液(确定它是不是有效的,但为了好玩):

public static void Main() 
{ 
    var a = new[] {1, 2, 3}; 
    var b = new[] {'a', 'b'}; 

    var joined = a.Join(
     b, 
     x => Array.IndexOf(a, x) % b.Length, 
     y => Array.IndexOf(b, y) % a.Length, 
     (x, y) => new object[]{ x, y } 
    ); 

    var flat = joined.SelectMany(x => x); 

    Console.Write(string.Join(", ", flat)); 
} 
0

另一种方法

public static IEnumerable<T> Mesh<T>(this IEnumerable<T> source, params IEnumerable<T>[] others) 
{ 
    var enumerators = new[] { source.GetEnumerator() }.Concat(others.Select(o => o.GetEnumerator())).ToList(); 

    var finishes = new bool[enumerators.Count]; 
    var allFinished = false; 
    while (!allFinished) 
    { 
     allFinsihed = true; 
     for (var i = 0; i < enumerators.Count; i++) 
     { 
      IEnumerator<T> enumerator = enumerators[i]; 
      if (!enumerator.MoveNext()) 
      { 
       finishes[i] = true; 
       enumerator.Reset(); 
       enumerator.MoveNext(); // Not sure, if we need it here, Reset says: BEFORE the first element 
      } 

      yield return enumerator.Current; 
      if (!finishes[i]) 
      { 
       allFinished = false; 
      } 
     } 
    } 
} 
+0

这看起来像Jodrell答案的复制粘贴,几乎没有变化(重置枚举数)。对你感到羞耻 –

+0

我认为额外的'MoveNext()'是个不错的主意。这不是我在调用'GetEnumerator()'时所期待的。 – Jodrell

+0

这会产生类似于{1,'A',2,'B',3,'C',4,'A',5,'B',1,'C'}的东西。所以它经常重复一次。 – dwonisch

0

你可以写你自己的扩展,允许更大的灵活性

注意:这个答案不会分配大量不必要的内存,并且只枚举一次所有序列。

我已经包含参数检查。

public static IEnumerable<T> Mesh<T>(
    this IEnumerable<T> source, 
    params IEnumerable<T>[] others) 
{ 
    if (others.LongLength == 0L) 
    { 
     foreach (var t in source) 
     { 
      yield return t; 
     } 

     yield break; 
    } 

    var nullCheck = Array.FindIndex(others, e => e == null); 
    if (nullCheck >= 0) 
    { 
     throw new ArgumentNullException(string.Format(
      "Parameter {0} is null, this is not supported.", 
      ++nullCheck), 
      (Exception)null); 
    } 

    var enumerators = new[] { source.GetEnumerator() } 
     .Concat(others.Select(o => o.GetEnumerator())).ToList(); 

    try 
    { 
     var finishes = new bool[enumerators.Count]; 
     var allFinished = false; 
     while (!allFinished) 
     { 
      allFinsihed = true; 
      for (var i = 0; i < enumerators.Count; i++) 
      { 
       if (finishes[i]) 
       { 
        continue; 
       } 

       if (enumerators[i].MoveNext()) 
       { 
         yield return enumerators[i].Current; 
         allFinished = false; 
         continue; 
       } 

       finishes[i] = true; 
      } 
     } 
    } 
    finally 
    { 
     foreach (var enumerator in enumerators) 
     { 
      enumerator.Dispose(); 
     } 
    } 
} 

这将让您“网格”任意数量的锯齿序列,只要有项目有相同的类型。例如

var a = new[] { 'a', 'b', 'c' }; 
var b = new[] { '1', '2', '3' }; 
var c = new[] { 'x', 'y' }; 
var d = new[] { '7', '8', '9', '0' }; 

var meshed = a.Mesh(b, c, d); 

会产生

{ 'a', '1', 'x', '7', 'b', '2', 'y', '8', 'c', '3', '9', '0' } 

如果你想混合类型,你可以做

var a = new[] { 'a', 'b', 'c' }; 
var b = new[] { 1, 2, 3 }; 

var meshed = a.Cast<object>().Mesh(b.Cast<object>); 

产生类似,

{ ('a'), (1), ('b'), (2), ('c'), (3) } 
+1

@SergeyBerezovskiy,并不是所有的枚举器都支持'Reset()',我确保所有创建的都被处理掉了。 – Jodrell

+0

纠正将产生{1,'A',2,'B',3,4,5}的编译时错误。所以它不像预期的那样重复出现。 – dwonisch

+0

@woni,我发现了一个编译错误并修复了它。你做了什么测试来获得意想不到的结果? – Jodrell

相关问题