【问题标题】:Compare Two Lists Via LINQ通过 LINQ 比较两个列表
【发布时间】:2016-07-28 18:08:40
【问题描述】:

我有两个列表会说 ListA 和 ListB。我需要遍历 ListB 并将 ID 与 ListA 进行比较。如果有匹配项,那么我需要从 ListB 中删除该项目并将其替换为 ListA 中的匹配项目/对象。

我一直在看THIS 文章。我也看过Intersect。但我真的不确定如何让它与 Linq 一起使用。

这是我的代码:

ListB 是在 else where 生成并传入的查询

var itemsForListA = Context.Set<Item>().AsQueryable();
var ListA = from i in itemsForListA 
            where i.ReplacementItemID != null
                  && (i.ItemStatus == "DISC" || i.ItemStatus == "ACT" 
                  && i.StoreID == null)
            select i;

foreach (var i in ListB)
{
    ListB = ListA.Where(x => x.Id == ListA.Id);
}

我以为我可以做这样的事情。我是否必须首先在 ListB 中找到 id 并将其删除,然后将新 id 从 ListA 附加到 B?

【问题讨论】:

    标签: c# linq ienumerable


    【解决方案1】:

    我认为您可以在 linq 中使用左连接,如下所示。

    var list = from lb in ListB
        join la in ListA
            on lb.Id equals la.Id into ListC
        from lc in ListC.DefaultIfEmpty()
        select lc ?? lb;
    

    它不会删除和替换项目,但会给出相同的结果,您可以重新分配给ListB

    【讨论】:

    • select obj = la ?? lb 就足够了(假设两个列表中的项目是相同的类型)。这不应该被否决。
    • Scott Hannen 做了一些excellent testing 表明这种方法一点也不差。
    • 感谢格特·阿诺德
    【解决方案2】:

    通过IEqualityComparer 使用内置的Enumerable.Except() 方法。

    要比较的类

    public class Item
    {
      public int Id { get; set; }
    }
    

    比较器

    public class ItemIdComparer<Item>
    {
      public bool Equals(Item left, Item right)
      {
        return left.Id == right.Id;
      }
      public int GetHashCode(Item item)
      {
        return item.Id.GetHashCode();
      }
    }
    

    用法

    var all = new List<Item>();
    var existing = new List<Item>();
    
    var nonExisting = all.Except(existing, new ItemIdComparer())
    

    我无法准确地测试这个......但它应该非常接近。

    【讨论】:

      【解决方案3】:

      你可以这样做:

      var list = listB.Join(listA, x => x.Id, y => y.Id, (x, y) => y).ToList();
      
      list.AddRange(listB.Where(b => listA.Any(c => c.Id != b.Id)).ToList());
      
      1. 首先,我将listA 与listB 以及从listA 中选择的具有相同ID 的对象进行比较。
      2. 我将 listB 中的元素添加到所选对象中,这些元素与 listA 没有共同 id。

      【讨论】:

      • y.Id 出现此错误“错误 CS1061 'int' 不包含 'Id' 的定义,并且找不到接受“int”类型的第一个参数的扩展方法 'Id'(是您缺少 using 指令或程序集引用?)
      • 您说比较必须通过 Id 完成,为此,您需要在两个列表对象中都有 id 属性
      【解决方案4】:

      我不确定你的 model 是什么样子,所以我创建了自己的模型:

      public class Person
      {
          public int Id { get; set; }
          public string FullName { get; set; }
          public byte Age { get; set; }
      }
      

      创建相等比较器:

      public class PersonComaparer : IEqualityComparer<Person>
      {
          public bool Equals(Person x, Person y)
          {
              if (x == null && y == null)
                  return true;
      
              if ((x == null && y != null)
                  || (x != null && y == null))
                  return false;
      
              return x.Id == y.Id;
          }
      
          public int GetHashCode(Person obj)
          {
              if (obj == null)
                  return 0;
      
              return obj.Id.GetHashCode();
          }
      }
      

      然后用它们来比较列表:

      List<Person> ListA = new List<Person>
      {
          new Person { Id = 1, FullName = "Someone" }
      };
      
      List<Person> ListB = new List<Person>
      {
          new Person { Id = 1 },
          new Person { Id = 2 }
      };
      
      PersonComaparer comparer = new PersonComaparer();
      ListB = ListA
          .Intersect(ListB, comparer)
          .Union(ListB, comparer)
          .ToList();
      

      结果将是:

      Id  |   Full name
      1       Someone
      2       null
      

      【讨论】:

        【解决方案5】:

        我将下面的答案与 Bapaiah Malasani 使用 10,000 个项目的join 解决方案进行了比较。我的解决方案始终需要六秒钟或更长时间。他的持续时间为 10 毫秒或更短。哎哟。我得回去看看我写的一大堆代码。


        您应该使用IEqualityComparer&lt;Item&gt; 吗?我会的,因为你可能会有很多很多这样的代码。一次编写,永久使用:

        public class ItemComparer: IEqualityComparer<Item>
        {
            public bool Equals(Item i1, Item i2)
            {
                if(i1 == null && i2 == null) return true;
                if(i1 == null ^ i2 ==null) return false;
                return(i1 == i2 || i1.Id == i2.Id);
            }
        
            public int GetHashCode(Item item)
            {
                return item != null ? item.Id.GetHashcode() : 0;
            }
        }
        

        然后,您可以将整个内容封装在一个更易于阅读的函数中。通过一系列Linq 声明,您可能不清楚您的意图是什么。但是一个具有清晰名称的函数会有所帮助:

        IEnumerable<T> ReplaceMatchingItems<T>(IEnumerable<T> discardFrom,
            IEnumerable<T> replaceWith,
            IEqualityComparer<T> comparer = null)
            {
                //prevent multiple enumerations
                var discardFromArray = discardFrom as T[] ?? discardFrom.ToArray();
                var replacements = replaceWith.Where(item => discardFromArray.Contains(item, comparer)).ToArray();
                var newList = discardFromArray.Except(replacements, comparer).ToList();
                newList.AddRange(replacements);
                return newList;
            }
        

        现在,您想要的原始函数如下所示:

        var listWithReplacements = ReplaceMatchingItems(ListB, ListA, new ItemComparer());
        

        一开始看起来像更多代码,但IEqualityComparer 会为您节省大量时间。当有人看到像 ReplaceMatchingItems 这样的函数调用时,他们更有可能理解代码在做什么,而不是仅仅查看一堆 Linq 查询。

        【讨论】:

        • 你也有 一堆 Linq 查询,但是包装在一个方法中。我认为将它包装在一个方法中是一个好主意,但它也可能包含来自另一个答案的“外部连接”(GroupJoin)。外连接将比您多次迭代序列的代码执行得更好。
        • 是的,无论哪种方式,您都有相同的查询。不同之处在于,当您阅读调用函数时,您不必查看它们并弄清楚它们在做什么。你只看到函数调用。因此,如果您不关心该功能的详细信息,则不必阅读它们。然后在所有这些查询所在的地方,很容易知道它们在做什么,因为它们位于一个具有描述它们的名称的函数中。
        • 我喜欢左连接解决方​​案。如果你说它表现更好,我倾向于相信你的话。但现在我很好奇,所以也许我会进行一些测试。在我的环境中,性能差异无关紧要,但对其他人来说可能。
        • 我用 10,000 个项目并排测试。不是非常科学,但足够有启发性。我的方法始终在 6-8 秒左右。使用连接的方法始终10 ms。哇。这不是扯皮。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多