使用LINQ向集合中添加元素

11
我正在尝试使用C#中的函数式方法处理一些列表。
我的想法是,我有一个包含T和double的Tuple集合,我想要更改某个元素T的Item 2。
由于数据是不可变的,所以函数式的做法是取出列表,过滤所有元素,其中元素与要更改的元素不同,并添加一个新的元组以存储新值。
我的问题是我不知道如何将元素附加到末尾。我希望能够这样做:
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
   return collection.Where(x=>!x.Item1.Equals(term)).Append(Tuple.Create(term,value));
}

但是没有Append方法。还有其他的方法吗?

请参见https://dev59.com/5HRB5IYBdhLWcg3wxZxK,该问题与不可变集合上的非变异添加方法的最佳名称有关。 - AakashM
同意。虽然我希望能够找到一种不需要创建新列表的解决方案。 - SRKX
7个回答

11
我相信您正在寻找Concat运算符。
它可以将两个IEnumerable<T>连接在一起,以便您可以使用单个项目创建一个连接。
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
   var newItem = new List<Tuple<T,double>>();
   newItem.Add(new Tuple<T,double>(term,value));
   return collection.Where(x=>!x.Item1.Equals(term)).Concat(newItem).ToList();
}

但concat需要另一个IEnumerable,这意味着实例化一个新列表,有点浪费资源,不是吗? - SRKX
你需要将该方法泛型化,并在返回语句中调用ToList。这样做之后就可以正常工作了。 - Ray

6
似乎.NET 4.7.1添加了Append LINQ运算符,这正是你所需要的。与Concat不同,它只使用一个值。
顺便说一句,如果声明泛型方法,应该在方法名后包含类型参数:
public List<Tuple<T, double>> Replace<T>(List<Tuple<T, double>> collection, T term, double value)                                     
{
    return collection.Where(x => !x.Item1.Equals(term))
                     .Append(Tuple.Create(term, value))
                     .ToList();
}

4
LINQ 不适用于变异。 函数式编程避免变异。
因此:
public IEnumerable<Tuple<T,double>> Extend(IEnumerable<Tuple<T,double>> collection, 
   T term,double value)
{
   foreach (var x in collection.Where(x=>!x.Item1.Equals(term)))
   {
     yield return x;
   }
   yield return Tuple.Create(term,value);
}

2
我的 Append 函数不会改变原列表。它只是将元素附加到一个新列表中并返回该新列表。 - SRKX

3
如果您愿意使用额外的包,请查看Nuget上提供的MoreLinq。这将为Concat函数提供一个新的重载:
public static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail);

这个函数完全按照要求执行,例如你可以这样做:
var myEnumerable = Enumerable.Range(10, 3); // Enumerable of values 10, 11, 12
var newEnumerable = myEnumerable.Concat(3); // Enumerable of values 10, 11, 12, 3

如果您喜欢LINQ,您可能也会喜欢其他许多新功能!

此外,正如在MoreLinq Github页面上指出的那样,该函数

public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element);

在 .NET Core 中,有一个不同名称但功能相同的函数可用,因此将来可能会看到它适用于 C#。


1

一种方法是使用.Concat(),但您需要拥有一个可枚举的而不是单个项目作为第二个参数。创建一个只有一个元素的数组确实可以工作,但写起来很麻烦。

最好编写一个自定义扩展方法来完成此操作。

一种方法是创建一个新的List<T>,并将第一个列表中的项目和第二个列表中的项目添加到其中。然而,最好使用yield关键字,这样您就不需要创建一个列表,并且可枚举将以惰性方式进行评估:

public static class EnumerableExtensions
{
    public static IEnumerable<T> Concat<T>(this IEnumerable<T> list, T item)
    {
        foreach (var element in list)
        {
            yield return element;
        }
        yield return item;
    }
}

1
这应该能够实现你想要的功能(虽然它在内部使用了变异,但从调用者的角度来看,它感觉是函数式的):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  var result = collection.Where(x => !x.Item1.Equals(term)).ToList();
  result.Add(Tuple.Create(term, value));
  return result;
}

另一种实现方式是使用 "map"(在 LINQ 中为 select):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  return collection.Select(x => 
    Tuple.Create(
      x.Item1, 
      x.Item1.Equals(term) ? value : x.Item2)).ToList();
}

但它可能会给你与原意不同的结果。尽管对我来说,当我看到一个名为Replace的方法时,我认为它是就地替换。

更新

你也可以像这样创建你想要的:

public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
  return collection.
    Where(x => !x.Item1.Equals(term)).
    Append(Tuple.Create(term, value)).
    ToList();
}

使用 Oded 提到的 Concat

public static class EnumerableEx {
  public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) {
    return source.Concat(new T[] { item });
  }
}

为什么它不起作用?你的答案肯定不是我,但我的问题中提到的Append方法有什么问题呢? - SRKX
1
好的,我明白了,你的append只是在末尾创建一个新元素的新列表。这样做确实是函数式的。我会删除关于“更加函数式”的评论,改为“另一种选择”。谢谢你的澄清。 - Jordão

0

我能找到的最接近的答案来自于这篇帖子,其内容为:

return collection.Where(x=>!x.Item1.Equals(term)).Concat(new[]{Tuple.Create(term,value)});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接