【发布时间】: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 呢?
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?
【问题讨论】:
你可以很容易地使用修改后的“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;
}
}
}
【讨论】:
operation。我们最终将其修改为使用 default(T) 传递缺少的操作数来调用操作。谢谢!
使用 .NET 4.0 的 Zip 运算符:
var sums = b.Zip(a, (x, y) => x + y)
.Concat(b.Skip(a.Count()));
如果您想概括这一点,请检查哪个有更多元素并将其用作上面的“b”。
【讨论】:
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()。
a.Count > n ? a[n] : 0 可能会更好。
我不得不稍微调整 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);
}
}
}
}
【讨论】:
仅使用 Zip 的方法是:
b.Zip(a.DefaultIfEmpty(), (x,y) => x+y)
由于数字类型的默认值为0,因此您不需要任何额外的处理。
【讨论】:
DefaultIfEmpty() 不会按照您的想法去做。如果序列a 为空,则返回单个默认元素。它不为已经存在的序列提供额外的项目!
这个怎么样:
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();
【讨论】:
以下是您的问题的解决方案。
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]);
【讨论】:
丑陋的 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]);
【讨论】:
在这种情况下,列表的长度是相同的还是不同的,这并不重要。 .NET 类库没有 Enumerable.Zip 方法来组合两个序列(它只会出现在 .NET 4.0 中),无论哪种方式,您都需要类似的东西。所以你要么必须写一个循环,要么写你自己的Zip(这仍然会涉及一个循环)。
有一些技巧可以在没有循环的单个 LINQ 查询中压缩这一切,包括加入索引,但这些会非常缓慢而且毫无意义。
【讨论】:
1 以及额外的 2 和 3 发生了什么?如果您正在寻找不同的值:
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'
【讨论】:
twoIter.MoveNext() 而不检查值时,然后访问twoIter.Current,或者在您知道oneIter.MoveNext() 已返回时访问oneIter.Current 的第二个while 循环中假的。
这里还有 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 可以,然后Skip 和Union 其余的:
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));
【讨论】:
结合Marc 和Damian 的答案,您可以只使用此代码,它更适合生产:
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);
}
}
}
}
【讨论】:
您可以用 c 压缩 b,其中 c 具有相同的大小:
var c = a.Concat(Enumerable.Repeat(0, b.Length - a.Length));
【讨论】:
我使用循环的实现:
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 完成此操作。这只是尝试提供最短且最易读的基于循环的实现。
【讨论】: