【问题标题】:Find items in one list that aren't in another list [duplicate]在一个列表中查找不在另一个列表中的项目[重复]
【发布时间】:2020-01-08 16:00:48
【问题描述】:

我有 2 个对象列表,我想从第一个对象中获取所有对象,其中字符串 a 与第二个列表中的字符串 a 不匹配。

public class ObjectA  
{  
    string Item;  
    int b;  
}

public class ObjectB  
{  
    string Item;  
    int b;  
}

使用 linq 可以通过这种方式轻松完成,但有什么更快的方法呢?

var newList = objectAList.Where(a => !objectBList.Any(b => b.Item == a.Item)).ToList()

【问题讨论】:

  • 您的列表大小是多少?另一种方法是使用两个嵌套的 for 语句。但是说这会更快并没有说太多。你应该做一些基准测试。小尺寸列表的行为可能可以忽略不计。对于中型或大型可能不会。什么是小?什么是大?使用所有这些问题,我试图说服您,如果您没有找到解决相同问题的另一种方法然后执行基准测试,那么这没有任何意义。
  • 您在寻找更快的方式、非 LINQ 方式还是同样更快的非 LINQ 方式?你有解决这个问题的想法或尝试吗?
  • @Christos 嵌套的语句为 O(n*m)。 Linq 的 except 明显更好。
  • @EricJ。我同意。我的想法是,当您只有一种解决方案时,试图回答哪个更快是没有意义的。即使有两种解决方案,如果您不运行任何基准测试,任何猜测都可能毫无意义。

标签: c# .net


【解决方案1】:

Linq except 方法就是为此目的而设计的,而且速度非常快。但是,您有一个问题,即您的两个类具有兼容的字段,但它们是不同的对象。这是处理它的一种方法:

class ObjectBase
{
    public string Item;
    public int b;
}

class ObjectA : ObjectBase
{

}

class ObjectB : ObjectBase
{

}

class ObjectComparer : IEqualityComparer<ObjectBase>
{
    public bool Equals(ObjectBase a, ObjectBase b)
    {
        return a?.Item == b?.Item; 
    }
    public int GetHashCode(ObjectBase o)
    {
        return o.?Item?.GetHashCode() ?? 0;
    }
}

// Very fast compared to your current approach. 1000x for my test case.

var newList = objectAList.Except(objectBList, new ObjectComparer()).ToList();

【讨论】:

    【解决方案2】:

    这个怎么样 - 没有 linq,仍然很好和流利,为正确的类型编辑:

    ObjectBList.RemoveAll(p => ObjectAList.Find(p2 => p2.Item == p.Item) != null ? true : false);
    

    完整示例:

    public class ObjectBase {
        public string Item;
        public int b;
    }
    
    public class ObjectA : ObjectBase{ }
    
    public class ObjectB : ObjectBase { }
    
    public List<ObjectB> Testing() {
        var list1 = new List<ObjectA> { new ObjectA { Item = "str1", b = 0 } };
        var list2 = new List<ObjectB> { new ObjectB { Item = "str1", b = 0 }, new ObjectB { Item = "str2", b = 1 } };
    
        // Key Line - Remove all from list2 found in list1
        list2.RemoveAll(p => list1.Find(p2 => p2.Item == p.Item) != null ? true : false);
    
        return list2;
    }
    

    【讨论】:

    • 这不会作用于 OP 在他的问题中的对象。
    • 是的,您必须使用带有 Contains 的 IEqualityComparer 才能在 Reference 类型上进行这项工作,并仅检查一个属性以进行比较。但这并没有太大的不同 - RemoveAll() 和 Contains() 是在这种情况下避免 Linq 的关键。我会尽快更新我的答案...
    • 已编辑,我确实必须更改它。更容易使用 .Find() 而不是 Contains 和 Comparer 等等。
    【解决方案3】:

    更快的方法是:

    var newList = objectAList.Select(a => a.Item).Except(objectBList.Select(b => b.Item));
    

    是的,我知道它是 Linq,但您要求更快的方法 :)

    HTH

    【讨论】:

    • 除了是要走的路,但这只会给你项目,而不是实际的对象。
    【解决方案4】:

    你可以试试这个代码:

    public static void Main(string[] args)
    {
      List<ObjectA> listA = new List<ObjectA>()
      {
        new ObjectA(){Item = "abc" },
        new ObjectA(){Item = "ab" },
      };
      List<ObjectB> listB = new List<ObjectB>()
      {
        new ObjectB(){Item = "abc" },
      };
      // loop backwards removing entry if it is found in the other list
      for (int i = listA.Count - 1; i >= 0; i--)
        if (listB.Find(e => e.Item == listA[i].Item) != null)
          listA.RemoveAt(i);
    }
    

    我将(你的和我的)两种方法都运行了五次,并在几毫秒内得到了以下结果(每次循环重复算法,100000 次迭代):

    我的方法:107 46 94 67 91

    您的方法:108 267 171 138 173

    这也可能是由于额外的ToList() 调用并创建了新对象newList

    所以,总而言之,如果有任何改进,那只是很小的一部分,我不会为此牺牲出色的 LINQ 方法提供的可读性。

    此外,它们在内部被设计为尽可能快地工作,所以我会依赖它们:)

    【讨论】:

    • RemoveAt 相当慢,因为它是一个 O(N) 操作,您必须执行 M 次(其中 M 是要删除的相交元素的数量),所以整体算法是 O(N* M)。
    【解决方案5】:

    时间上最快的方法是使用 HashSet,尤其是对于大型列表:

        private List<ObjectA> Find(List<ObjectA> list1, List<ObjectB> list2)
        {
            var list2HashSet = list2.Select(x => x.Item).ToHashSet();
            return list1.Where(x => !list2HashSet.Contains(x.Item)).ToList();
        }
    

    注意:并且不要忘记将属性Item公开,否则它将不起作用!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-10
      • 2017-04-28
      • 2022-11-12
      • 2017-06-06
      • 2019-12-31
      • 1970-01-01
      • 2017-06-02
      相关资源
      最近更新 更多