【问题标题】:Add two Lists of different length in C#在C#中添加两个不同长度的列表
【发布时间】:2022-05-23 22:45:44
【问题描述】:
List<double> a = new List<double>{1,2,3};
List<double> b = new List<double>{1,2,3,4,5};

a + b 应该给我{2,4,6,4,5}

显然我可以写一个循环,但有没有更好的方法?使用 LINQ?

【问题讨论】:

  • 您是否在寻找不同的值(没有重复?另外,1 呢?

标签: c# linq


【解决方案1】:

你可以很容易地使用修改后的“zip”操作,但没有内置任何东西。像:

    static void Main() {
        var a = new List<int> { 1, 2, 3 };
        var b = new List<int> { 1, 2, 3, 4, 5 };
        foreach (var c in a.Merge(b, (x, y) => x + y)) {
            Console.WriteLine(c);
        }
    }
    static IEnumerable<T> Merge<T>(this IEnumerable<T> first,
            IEnumerable<T> second, Func<T, T, T> operation) {
        using (var iter1 = first.GetEnumerator())
        using (var iter2 = second.GetEnumerator()) {
            while (iter1.MoveNext()) {
                if (iter2.MoveNext()) {
                    yield return operation(iter1.Current, iter2.Current);
                } else {
                    yield return iter1.Current;
                }
            }
            while (iter2.MoveNext()) {
                yield return iter2.Current;
            }
        }
    }

【讨论】:

  • 为什么你的解决方案这么长?
  • 因为它可以与任意数量的序列和操作重用——不仅仅是列表/索引器/添加。固定垂直空间...更好?
  • 只有在两个列表同时出现时才有效。查看我提出的解决方案,其中考虑了不同类型的列表。
  • @IRBMe - 我不敢苟同。注意 MoveNext 的测试和处理。坦率地说 - 它是正确的。
  • 不存在的项目不会调用operation。我们最终将其修改为使用 default(T) 传递缺少的操作数来调用操作。谢谢!
【解决方案2】:

使用 .NET 4.0 的 Zip 运算符:

var sums = b.Zip(a, (x, y) => x + y)
            .Concat(b.Skip(a.Count()));

如果您想概括这一点,请检查哪个有更多元素并将其用作上面的“b”。

【讨论】:

  • "检查哪个元素更多,并将其用作上面的“b”。" - 如何?
  • @firsttimer (a.Count > b.Count) ? a.Skip(b.Count) : b.Skip(a.Count)
【解决方案3】:
Enumerable.Range(0, new[] { a.Count, b.Count }.Max())
    .Select(n => a.ElementAtOrDefault(n) + b.ElementAtOrDefault(n));

【讨论】:

  • 就个人而言,我会使用Math.Max(a.Count, b.Count) 而不是new[] { a.Count, b.Count }.Max()
  • ElementAtOrDefault,对于 IEnumerable,将(可能)遍历整个列表。改用更长的a.Count &gt; n ? a[n] : 0 可能会更好。
【解决方案4】:

我不得不稍微调整 Marc 的解决方案以供我使用以允许不同类型的列表,所以我想我会发布以防其他人需要它。

public static IEnumerable<TResult> Merge<TFirst,TSecond,TResult>(this IEnumerable<TFirst> first,
            IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> operation) {
    using (var iter1 = first.GetEnumerator()) {
        using (var iter2 = second.GetEnumerator()) {
            while (iter1.MoveNext()) {
                if (iter2.MoveNext()) {
                    yield return operation(iter1.Current, iter2.Current);
                } else {
                    yield return operation(iter1.Current, default(TSecond));
                }
            }
            while (iter2.MoveNext()) {
                yield return operation(default(TFirst),  iter2.Current);
            }
        }
    }
}

【讨论】:

    【解决方案5】:

    仅使用 Zip 的方法是:

    b.Zip(a.DefaultIfEmpty(), (x,y) => x+y)
    

    由于数字类型的默认值为0,因此您不需要任何额外的处理。

    【讨论】:

    • 当心:DefaultIfEmpty() 不会按照您的想法去做。如果序列a 为空,则返回单个默认元素。它不为已经存在的序列提供额外的项目!
    【解决方案6】:

    这个怎么样:

    List<double> doubles = Enumerable.Range(0, Math.Max(a.Count, b.Count))
        .Select(x => (a.Count > x ? a[x] : 0) + (b.Count > x ? b[x] : 0))
        .ToList();
    

    【讨论】:

    • 如果我的 a/b 集合是 IEnumerable 而不是列表怎么办?
    • 在这种情况下,为了提高效率,您必须同时迭代两个列表,因此请使用 Marc Gravell 的实现。
    【解决方案7】:

    以下是您的问题的解决方案。

    List<double> a = new List<double>{1,2,3};
    List<double> b = new List<double>{1,2,3,4,5};
    
    List<double> sum = new List<double>();
    int max = Math.Min(a.Count, b.Count);
    for (int i = 0; i < max; i++){
        sum.Add(a[i] + b[i]);
    }
    
    if (a.Count < b.Count)
        for (int i = max i < b.Count)
            sum.Add(b[i]);
    else
        for (int i = max i < a.Count)
        sum.Add(a[i]);
    

    【讨论】:

    • 来自 OP:“显然我可以写一个循环,但有更好的方法吗?”
    【解决方案8】:

    丑陋的 LINQ 解决方案:

    var sum = Enumerable.Range(0, (a.Count > b.Count) ? a.Count : b.Count)
        .Select(i => (a.Count > i && b.Count > i) ? a[i] + b[i] : (a.Count > i) ? a[i] : b[i]);
    

    【讨论】:

      【解决方案9】:

      在这种情况下,列表的长度是相同的还是不同的,这并不重要。 .NET 类库没有 Enumerable.Zip 方法来组合两个序列(它只会出现在 .NET 4.0 中),无论哪种方式,您都需要类似的东西。所以你要么必须写一个循环,要么写你自己的Zip(这仍然会涉及一个循环)。

      有一些技巧可以在没有循环的单个 LINQ 查询中压缩这一切,包括加入索引,但这些会非常缓慢而且毫无意义。

      【讨论】:

      • 为什么会得到-1?如果事实不正确,请指出具体问题。否则,我看不出这不是对所述问题的直接答案(“我可以编写一个循环,但有更好的方法吗?使用 linq?”)。
      • 我的解决方案使用没有循环的 LINQ。显然LINQ会做循环,但它会由编译器编写。
      【解决方案10】:

      1 以及额外的 23 发生了什么?如果您正在寻找不同的值:

      var one = new List<int> { 1, 2, 3 };
      var two = new List<int> { 1, 2, 3, 4, 5 };
      
      foreach (var x in one.Union(two)) Console.Write("{0} ", x);
      

      会给你1 2 3 4 5

      如果您只是在寻找附加到第一个列表的第二个列表,那么:

      foreach(var x in one.Concat(two)) // ...
      

      会给你1 2 3 1 2 3 4 5

      编辑:哦,我明白了,您正在寻找一种Zip,但它返回了额外的部分。试试这个:

      public static IEnumerable<V> Zip<T, U, V>(
          this IEnumerable<T> one,
          IEnumerable<U> two,
          Func<T, U, V> f)
      {
          using (var oneIter = one.GetEnumerator()) {
              using (var twoIter = two.GetEnumerator()) {
                  while (oneIter.MoveNext()) {
                      twoIter.MoveNext();
                      yield return f(oneIter.Current,
                          twoIter.MoveNext() ?
                              twoIter.Current :
                              default(U));
                  }
      
                  while (twoIter.MoveNext()) {
                      yield return f(oneIter.Current, twoIter.Current);
                  }
              }
          }
      }
      

      这是一个更像普通 zip 函数的函数,它不返回额外内容:

      public static IEnumerable<V> Zip<T, U, V>(
          this IEnumerable<T> one,
          IEnumerable<U> two,
          Func<T, U, V> f)
      {
          using (var oneIter = one.GetEnumerator()) {
              using (var twoIter = two.GetEnumerator()) {
                  while (oneIter.MoveNext()) {
                      yield return f(oneIter.Current,
                          twoIter.MoveNext() ?
                              twoIter.Current :
                              default(U));
                  }
              }
          }
      }
      

      使用示例:

      var one = new List<int>  { 1, 2, 3, 4, 5};
      var two = new List<char> { 'h', 'e', 'l', 'l', 'o' };
      
      foreach (var x in one.Zip(two, (a,b) => new {A = a, B =b }))
          Console.WriteLine("{0} => '{1}'", x.A, x.B);
      

      结果:

      1 => 'h'
      2 => 'e'
      3 => 'l'
      4 => 'l'
      5 => 'o'

      【讨论】:

      • 他实际上是在尝试对两个列表中的元素进行成对求和,将较短列表中缺失的元素视为 0。
      • 这是不正确的; Current 的值未定义,如果您不知道 MoveNext 返回 true;实际上,对于较旧的迭代器,如果您这样做,它们会抛出异常;请参阅msdn.microsoft.com/en-us/library/…:“如果最后一次调用 MoveNext 返回 false,Current 也会引发异常,这表示集合结束。”
      • 具体来说,当您使用twoIter.MoveNext() 而不检查值时,然后访问twoIter.Current,或者在您知道oneIter.MoveNext() 已返回时访问oneIter.Current 的第二个while 循环中假的。
      • 根据这个可以正常工作:msdn.microsoft.com/en-us/library/… 使枚举数无效的唯一方法是在中途修改集合。如果您继续调用 GetNext,它只会继续返回 false,直到您调用 Reset。
      • 我认为你没有理解这个例子; 必须检查 MoveNext 的值;在您引用的页面中给出的示例中,不这样做将导致超出范围的异常。此外,Reset() 已被弃用,并且它需要(在语言规范中)对于大多数实现(即迭代器块)来引发异常。
      【解决方案11】:

      这里还有 3 个:

      使列表大小相同,然后只需简单的选择。

      (a.Count < b.Count ? a : b).AddRange(new double[Math.Abs(a.Count - b.Count)]);
      var c = a.Select((n, i) => n + b[i]);
      

      不要让它们的大小相同,而是通过最长的并检查最短的范围的结束(存储 shortList.Count 以便于提高性能):

      var longList = a.Count > b.Count ? a : b;
      var shortList = longList == a ? b : a;
      var c = longList.Select((n, i) => n + (shortList.Count > i ? shortList[i] : 0));
      

      Take 可以,然后SkipUnion 其余的:

      var c = a.Take(Math.Min(a.Count, b.Count))
               .Select((n, i) => n + b[i])
               .Union(a.Skip(Math.Min(a.Count, b.Count));
      

      【讨论】:

        【解决方案12】:

        结合MarcDamian 的答案,您可以只使用此代码,它更适合生产:

        public static class EnumerableExtensions
        {
            public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, T> operation)
            {
                return Merge<T, T, T>(first, second, operation);
            }
        
            public static IEnumerable<TResult> Merge<T, TResult>(this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, TResult> operation)
            {
                return Merge<T, T, TResult>(first, second, operation);
            }
        
            public static IEnumerable<TResult> Merge<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> operation)
            {
                if (first == null) throw new ArgumentNullException(nameof(first));
                if (second == null) throw new ArgumentNullException(nameof(second));
        
                using (var iter1 = first.GetEnumerator())
                using (var iter2 = second.GetEnumerator())
                {
                    while (iter1.MoveNext())
                    {
                        yield return iter2.MoveNext()
                            ? operation(iter1.Current, iter2.Current)
                            : operation(iter1.Current, default);
                    }
        
                    while (iter2.MoveNext())
                    {
                        yield return operation(default, iter2.Current);
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案13】:

          您可以用 c 压缩 b,其中 c 具有相同的大小:

          var c = a.Concat(Enumerable.Repeat(0, b.Length - a.Length)); 
          

          【讨论】:

            【解决方案14】:

            我使用循环的实现:

            List<double> shorter, longer;
            if (a.Count > b.Count)
            {
                shorter = b; longer = a
            }
            else
            {
                shorter = a; longer = b;
            }
            
            List<double> result = new List<double>(longer);
            for (int i = 0; i < shorter.Count; ++i)
            {
                 result[i] += shorter[i];
            }
            

            请注意,这是对my other answer 的补充,它解释了为什么在 3.5 中无法通过 LINQ 完成此操作。这只是尝试提供最短且最易读的基于循环的实现。

            【讨论】:

              猜你喜欢
              • 2017-08-30
              • 1970-01-01
              • 1970-01-01
              • 2022-11-16
              • 2017-10-15
              • 2021-02-25
              • 1970-01-01
              • 2019-10-16
              • 2021-02-10
              相关资源
              最近更新 更多