【问题标题】:Merge different length arrays without losing a value using Zip()使用 Zip() 合并不同长度的数组而不丢失值
【发布时间】:2015-05-05 03:38:07
【问题描述】:

在下面的代码中,我合并了intstring 类型的两个数组。第一个的长度大于第二个,因此最后一个索引(5)没有被合并:

int[] numbers = new[] { 1, 2, 3, 4, 5 };
string[] words = new string[] { "one", "two", "three", "four" };

var numbersAndWords = numbers.Zip(words, (n, w) => new { Number = n, Word = w });
foreach (var nw in numbersAndWords)
{
    Console.WriteLine(nw.Number + nw.Word);
}

我想知道一种将其合并的方法。例如,在words 中存在的最后一个字符串之后创建null 或空字符串,并使用它与最后一个numbers 索引合并。想不通。

编辑: 结果我得到了

1one
2two
3three
4four

我想要的结果

1one
2two
3three
4four
5

谢谢!

编辑:不是重复的,我的另一个问题是关于在空对象上调用方法。

【问题讨论】:

    标签: c# .net arrays linq


    【解决方案1】:

    您可以轻松编写自己的类似 LINQ 的扩展方法来完成它:

    public static class MyEnumerable
    {
        public static IEnumerable<TResult> ZipWithDefault<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> selector)
        {
            bool firstMoveNext, secondMoveNext;
    
            using (var enum1 = first.GetEnumerator())
            using (var enum2 = second.GetEnumerator())
            {
                while ((firstMoveNext = enum1.MoveNext()) & (secondMoveNext = enum2.MoveNext()))
                    yield return selector(enum1.Current, enum2.Current);
    
                if (firstMoveNext && !secondMoveNext)
                {
                    yield return selector(enum1.Current, default(TSecond));
                    while (enum1.MoveNext())
                    {
                        yield return selector(enum1.Current, default(TSecond));
                    }
                }
                else if (!firstMoveNext && secondMoveNext)
                {
                    yield return selector(default(TFirst), enum2.Current);
                    while (enum2.MoveNext())
                    {
                        yield return selector(default(TFirst), enum2.Current);
                    }
                }
            }
        }
    }
    

    但如果您的源始终是一对数组,那么简单地使用for 循环可能会更容易:

    public static IEnumerable<TResult> ZipWithDefault<TFirst, TSecond, TResult>(this TFirst[] first, TSecond[] second, Func<TFirst, TSecond, TResult> selector)
    {
        var maxLength = Math.Max(first.Length, second.Length);
    
        for(var i = 0; i < maxLength; i++)
        {
            var firstItem = i < first.Length ? first[i] : default(TFirst);
            var secondItem = i < second.Length ? second[i] : default(TSecond);
            yield return selector(firstItem, secondItem);
    
        }
    }
    

    【讨论】:

      【解决方案2】:

      您可以在压缩之前扩展两个集合中较小的一个,例如像这样:

      int[] numbers = new[] { 1, 2, 3, 4, 5 };
      string[] words = new string[] { "one", "two", "three", "four" };
      
      IEnumerable<string> wordsExtended = words;
      
      if(words.Length < numbers.Length)
      {
          wordsExtended = words.Concat(Enumerable.Repeat("", numbers.Length - words.Length));
      }
      
      var numbersAndWords = numbers.Zip(wordsExtended, (n, w) => new { Number = n, Word = w });
      foreach (var nw in numbersAndWords)
      {
          Console.WriteLine(nw.Number + nw.Word);
      }
      

      理想情况下,您希望将其封装在一个实用方法中并具有通用性,以便它适用于任何压缩的集合。

      看起来有人已经在这个 Programmers StackExchange answer 上写了一个通用实现。

      【讨论】:

      • 您链接到的答案的问题是它会导致源集合上的多个枚举(因为Count 调用。使用数组作为源时无关紧要,但在使用不同的来源。
      【解决方案3】:

      这是一个非对称解决方案,它使用第一个序列作为锚点,在可用时将第二个序列中的相应元素配对,否则使用默认值。不需要长度计算,也不需要对可枚举项进行早期强制计算。

      为 IEnumerable 添加扩展:

      public static class EnumerableExtensions {
              
      
          public static IEnumerable<T> PadRight<T>(this IEnumerable<T> s)
          {
              foreach (var t in s)
                  yield return t;
      
              while (true)
                  yield return default(T);
           }
      }
      

      在压缩之前使用扩展“填充”第二个序列:

      int[] numbers = new[] { 1, 2, 3, 4, 5 };
      string[] words = new string[] { "one", "two", "three", "four" };
      
      var numbersAndWords = numbers.Zip(words.PadRight(), (n, w) => new { Number = n, Word = w });
      foreach (var nw in numbersAndWords)
      {
          Console.WriteLine(nw.Number + nw.Word ?? string.Empty);
      }
      

      【讨论】:

      • 这仅在您事先知道哪个输入较短时才有效。它要求您通过在其上调用此 PadRight() 方法来显式处理较短的输入。与公认的答案相比,这似乎是一个明显较差的解决方案。我无法想象有人会选择它而不是更好、更通用的解决方案。事实上,这个答案几乎与this one 相同,有很多相同的缺点。
      • 感谢建设性的批评。我已将您的 cmets 纳入警告。我确实指出,建议的解决方案不需要强制枚举惰性序列(计算长度)。因此,它可能对某些应用程序更有效。
      • “建议的解决方案不需要强制枚举惰性序列”——当然,但接受的解决方案也不需要,并且不需要提前知道哪个序列会更短,就像你的一样。
      • 我认为这是一个有用的解决方案,在适当的情况下。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-08
      • 1970-01-01
      • 2015-05-29
      • 2021-12-27
      相关资源
      最近更新 更多