【问题标题】:Overlay/Join two collections with Linq使用 Linq 覆盖/连接两个集合
【发布时间】:2009-06-11 07:03:11
【问题描述】:

我有以下场景:

List 1 有 20 个 TItem 类型的项目,List 2 有 5 个相同类型的项目。清单 1 已包含清单 2 中的项目,但处于不同的状态。我想用列表 2 中的项目覆盖列表 1 中的 5 个项目。

我认为连接可能有效,但我想覆盖列表 1 中的项目,而不是将它们连接在一起并有重复项。

有一个唯一的键可以用来查找列表1中要覆盖的项目,该键的类型是int

【问题讨论】:

    标签: c# linq collections


    【解决方案1】:

    您可以使用内置的 Linq .Except(),但它需要一个 IEqualityComparer,因此请改用 .Except() 的 a fluid version

    假设一个具有您所指示的整数键的对象:

    public class Item
    {
        public int Key { get; set; }
        public int Value { get; set; }
        public override string ToString()
        {
            return String.Format("{{{0}:{1}}}", Key, Value);
        }
    }
    

    原始对象列表可以与更改后的对象列表合并,如下所示:

    IEnumerable<Item> original = new[] { 1, 2, 3, 4, 5 }.Select(x => new Item
        {
            Key = x,
            Value = x
        });
    IEnumerable<Item> changed = new[] { 2, 3, 5 }.Select(x => new Item
        {
            Key = x,
            Value = x * x
        });
    
    IEnumerable<Item> result = original.Except(changed, x => x.Key).Concat(changed);
    result.ForEach(Console.WriteLine);
    

    输出:

    {1:1}
    {4:4}
    {2:4}
    {3:9}
    {5:25}
    

    【讨论】:

      【解决方案2】:

      LINQ 不用于对底层数据源进行实际修改;它严格来说是一种查询语言。当然,您可以从List1List2 进行外部联接,如果它不为空,则选择List2 的实体,如果为空,则选择List1 的实体,但这会给您一个@ 987654325@的结果;它实际上不会修改集合。您可以对结果执行ToList() 并将其分配给List1,但这会改变引用;我不知道这是否会影响您的应用程序的其余部分。

      从字面上理解您的问题,因为您想将 List1 中的项目替换为 List2 中的项目(如果存在),那么您必须在 for 循环中手动执行此操作 List1,检查List2 中是否存在相应条目,并将List1 条目的索引替换为List2 中的条目。

      【讨论】:

        【解决方案3】:

        正如亚当所说,LINQ 是关于查询的。但是,您可以使用Enumerable.Union 以正确的方式创建一个 集合。不过,您需要创建一个合适的IEqualityComparer - 最好有UnionBy。 (可能是MoreLINQ 的另一个?)

        基本上:

        var list3 = list2.Union(list1, keyComparer);
        

        keyComparer 是比较两个键的实现。 MiscUtil 包含一个 ProjectionEqualityComparer,这会使这更容易一些。

        或者,您可以在串联后使用来自 MoreLINQ 的 DistinctBy

        var list3 = list2.Concat(list1).DistinctBy(item => item.Key);
        

        【讨论】:

          【解决方案4】:

          这是一个使用 GroupJoin 的解决方案。

                  List<string> source = new List<string>() { "1", "22", "333" };
                  List<string> modifications = new List<string>() { "4", "555"};
                    //alternate implementation
                  //List<string> result = source.GroupJoin(
                  //    modifications,
                  //    s => s.Length,
                  //    m => m.Length,
                  //    (s, g) => g.Any() ? g.First() : s
                  //).ToList();
          
                  List<string> result =
                  (
                      from s in source
                      join m in modifications
                      on s.Length equals m.Length into g
                      select g.Any() ? g.First() : s
                  ).ToList();
          
                  foreach (string s in result)
                      Console.WriteLine(s);
          

          嗯,我在做一个可重复使用的扩展方法怎么样:

              public static IEnumerable<T> UnionBy<T, U>
              (
                  this IEnumerable<T> source,
                  IEnumerable<T> otherSource,
                  Func<T, U> selector
              )
              {
                  return source.GroupJoin(
                      otherSource,
                      selector,
                      selector,
                      (s, g) => g.Any() ? g.First() : s
                          );
              }
          

          调用者:

          List<string> result = source
            .UnionBy(modifications, s => s.Length)
            .ToList();
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-01-18
            • 1970-01-01
            • 2014-12-10
            • 1970-01-01
            • 2018-09-15
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多