如何高效地从两个List分别移除在另一个List中拥有相同属性的对象

leon51 2019-08-06 03:03:17
以下两个List分别移除在另一个List中拥有相同的属性的Product(假设有Id,Name等属性),
请问有什么好方法?我现在是一个个的检查它们的属性是否相等。
var listA = new List<Product>();
var listB = new List<Product>();
...全文
1699 1 收藏 24
写回复
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
坎艺 2019-09-05
引用 6 楼 听雨停了的回复:
[quote=引用 5 楼 leon51 的回复:] 非常感谢你的回复,不过第26-29行看不懂,这个方法是否多余的?为何要传入参数obj而且始终返回0?
public int GetHashCode(Product obj)
{
	return 0;
}
这个方法不能说是多余的,不过我没发现他有啥用就是。你也不能删除他,删除就出问题了。这个方法是实现接口里面的。你可以随便return一个数字。[/quote] 重载gethashcode,伪置0
回复
FainSheeg 2019-08-14
引用 9 楼 github_36000833 的回复:
把Main(string[] args)改动一下,多增加一些数据,并打印操作时间。

static void Main(string[] args)
{
Random random = new Random();

List<Product> list1 = new List<Product>() {
new Product(1,"A"),
new Product(2,"B"),
new Product(3,"A"),
};
List<Product> list2 = new List<Product>() {
new Product(1,"A"),
new Product(2,"C"),
new Product(3,"D"),
};

for (int i = 0; i < 100 * 100; i ++)
{
list1.Add(new Product(random.Next(), "noname"));
list2.Add(new Product(random.Next(), "noname"));
}

var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var list3 = list1.Except(list2, new ProductCompara());//移除list1中list2中存在的项
var list4 = list2.Except(list1, new ProductCompara());//移除list2中list1中存在的项

list3.Count();
list4.Count();
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时15355毫秒。
}


然后改善GetHashCode
public int GetHashCode(Product obj)
{
return obj.Id; // Id是不错的HashCode(类似定位床铺的宿舍号)
}


重新运行Main(),我的机器的结果是6毫秒。

static void Main(string[] args)
{
//...
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时6毫秒。
}

回复
bloodish 2019-08-13
想高效就自己写Except操作,图方便就直接用Linq

        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public override int GetHashCode()
            {
                return ($"{Name}_{Id}").GetHashCode();
            }
        }

        static void Main(string[] args)
        {
            var list1 = new List<Product>();
            var list2 = new List<Product>();

            var dic1 = list1.ToDictionary(p => p, p => p);
            var dic2 = list2.ToDictionary(p => p, p => p);

            ExceptItems(list1, dic2);
            ExceptItems(list2, dic1);

            void ExceptItems(List<Product> listSrc, Dictionary<Product,Product> dicExcept)
            {
                var removeList = new List<Product>();

                listSrc.ForEach(p =>
                {
                    if (dicExcept.ContainsKey(p))
                    {
                        removeList.Add(p);
                    }
                });
                removeList.ForEach(p => listSrc.Remove(p));
            }
        }
回复
XBodhi. 2019-08-13
楼上的是个比较器。继承接口,自己实现,然后输入2个集合,然后合并。
回复
leon51 2019-08-12
引用 10 楼 github_36000833 的回复:
如果希望用同一套数据来进行比较,可以提供随机种子,来固定随机系列: static void Main(string[] args) { Random random = new Random(1234); ... 我机器的测试结果大致稳定。 优化前:15000毫秒 优化后:6毫秒
你好,如果我想让用户选择要比较的属性,比如只比较id、只比较name或全部比较,ProductCompara应该怎么改?
回复
m0_37646670 2019-08-08
两个集合之间取差集
回复
by_封爱 版主 2019-08-08
遍历是肯定的.. 但是如果是我,我比较喜欢采用另类的方式来说实现, 比如 把对象转换成json 对比字符串...
回复
desperaso 2019-08-08
不好意思有误
// 全部符合
var query1 = list2s.Where(s => list1.All(t => s.name.Contains(t))).ToList();
// 包含
var query2 = list2s.Where(s => list1.Any(t => s.name.Contains(t))).ToList();
// 不符合
var query3 = list2s.Where(s => list1.All(t => !s.name.Contains(t))).ToList();
// 不包含
var query4 = list2s.Where(s => list1.Any(t => !s.name.Contains(t))).ToList();
回复
desperaso 2019-08-08

public class list2
{
public int id;
public string name;
public list2(int d, string s)
{
id = d;
name = s;
}
}
public static List<list2> list2s = new List<list2>();

string[] list1 = new string[] { "A", "B", "C", "D" };

private void button1_Click(object sender, EventArgs e)
{
list2s.Add(new list2(1, "ABCDEFG"));
list2s.Add(new list2(2, "FFFFFFFFF"));
list2s.Add(new list2(3, "CCCCCCC"));
list2s.Add(new list2(4, "DFGHJUI"));
list2s.Add(new list2(5, "ZXZXZXZX"));
list2s.Add(new list2(6, "LOPIUYTC"));

// 全部符合
var query1 = list2s.Where(s => list1.All(t => s.name.Contains(t))).ToList();
// 包含
var query2 = list2s.Where(s => list1.All(t => s.name.Contains(t))).ToList();
// 不符合
var query3 = list2s.Where(s => list1.All(t => !s.name.Contains(t))).ToList();
// 不包含
var query4 = list2s.Where(s => list1.All(t => !s.name.Contains(t))).ToList();
}
回复
货郎大叔 2019-08-08
检查对象的属性是否相等,这是必经之路
回复
XBodhi. 2019-08-07
或者你直接 改造下 List 对象。附加方法直接在内部处理前就给分析出来,这个是最高效的,因为提早创建了缓冲区,然后对个对象内部处理。
回复
XBodhi. 2019-08-07
我觉得你还是要继承下 ICompare 接口。然后逐个出来。

因为这个东西还是要顺序查找。因为是 M *N 次
回复
听雨停了 2019-08-07
引用 8 楼 github_36000833 的回复:
[quote=引用 6 楼 听雨停了 的回复:] 这个方法不能说是多余的,不过我没发现他有啥用就是。你也不能删除他,删除就出问题了。这个方法是实现接口里面的。你可以随便return一个数字。
各个方法是用来帮忙比较,使它更有效率。 比如,学校有新生1万个,1万个宿舍床位,每个床位有独一的床位号。 如果知道了HashCode(比如用宿舍号HashCode),就非常容易定位到你的床位(一个房间不过就几张床)。 如果没有HashCode,就可能要遍历新生床位了。 因此,如果两个对象相等(床位相等),那么它们的HashCode(宿舍)必须相等。 如果两个对象的HashCode相等(宿舍相同),那么两个对象不一定就相等,还有待进一部比较(就是那个Equals函数)。 我可能在下帖给出效率差别的例子。 [/quote] 学习了
回复
github_36000833 2019-08-07
如果希望用同一套数据来进行比较,可以提供随机种子,来固定随机系列:
static void Main(string[] args)
{
Random random = new Random(1234);
...


我机器的测试结果大致稳定。
优化前:15000毫秒
优化后:6毫秒


回复
github_36000833 2019-08-07
把Main(string[] args)改动一下,多增加一些数据,并打印操作时间。

static void Main(string[] args)
{
Random random = new Random();

List<Product> list1 = new List<Product>() {
new Product(1,"A"),
new Product(2,"B"),
new Product(3,"A"),
};
List<Product> list2 = new List<Product>() {
new Product(1,"A"),
new Product(2,"C"),
new Product(3,"D"),
};

for (int i = 0; i < 100 * 100; i ++)
{
list1.Add(new Product(random.Next(), "noname"));
list2.Add(new Product(random.Next(), "noname"));
}

var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var list3 = list1.Except(list2, new ProductCompara());//移除list1中list2中存在的项
var list4 = list2.Except(list1, new ProductCompara());//移除list2中list1中存在的项

list3.Count();
list4.Count();
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时15355毫秒。
}


然后改善GetHashCode
public int GetHashCode(Product obj)
{
return obj.Id; // Id是不错的HashCode(类似定位床铺的宿舍号)
}


重新运行Main(),我的机器的结果是6毫秒。

static void Main(string[] args)
{
//...
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时6毫秒。
}

回复
github_36000833 2019-08-07
引用 6 楼 听雨停了 的回复:
这个方法不能说是多余的,不过我没发现他有啥用就是。你也不能删除他,删除就出问题了。这个方法是实现接口里面的。你可以随便return一个数字。


各个方法是用来帮忙比较,使它更有效率

比如,学校有新生1万个,1万个宿舍床位,每个床位有独一的床位号。
如果知道了HashCode(比如用宿舍号HashCode),就非常容易定位到你的床位(一个房间不过就几张床)。
如果没有HashCode,就可能要遍历新生床位了。

因此,如果两个对象相等(床位相等),那么它们的HashCode(宿舍)必须相等。
如果两个对象的HashCode相等(宿舍相同),那么两个对象不一定就相等,还有待进一部比较(就是那个Equals函数)。

我可能在下帖给出效率差别的例子。
回复
wanghui0380 2019-08-07
大师兄,说的对 不过其实都一样,无论那种方法都是遍历,只是换个写法。楼上所有的写法,其实都跟你自己写的没有任何区别,不存在高效这么一说。 用楼上那些手段 1只是人家帮你实现过,你可以简单调用 2.人家的是标准写法,你自己写大多数情况也如此,但偶尔喝醉了,黄昏了也能写出意外的东西,所以统一写法,防止意外
回复
听雨停了 2019-08-07
引用 5 楼 leon51 的回复:
非常感谢你的回复,不过第26-29行看不懂,这个方法是否多余的?为何要传入参数obj而且始终返回0?
public int GetHashCode(Product obj)
{
	return 0;
}
这个方法不能说是多余的,不过我没发现他有啥用就是。你也不能删除他,删除就出问题了。这个方法是实现接口里面的。你可以随便return一个数字。
回复
E次奥 2019-08-07
引用 9 楼 github_36000833 的回复:
把Main(string[] args)改动一下,多增加一些数据,并打印操作时间。

static void Main(string[] args)
{
Random random = new Random();

List<Product> list1 = new List<Product>() {
new Product(1,"A"),
new Product(2,"B"),
new Product(3,"A"),
};
List<Product> list2 = new List<Product>() {
new Product(1,"A"),
new Product(2,"C"),
new Product(3,"D"),
};

for (int i = 0; i < 100 * 100; i ++)
{
list1.Add(new Product(random.Next(), "noname"));
list2.Add(new Product(random.Next(), "noname"));
}

var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var list3 = list1.Except(list2, new ProductCompara());//移除list1中list2中存在的项
var list4 = list2.Except(list1, new ProductCompara());//移除list2中list1中存在的项

list3.Count();
list4.Count();
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时15355毫秒。
}


然后改善GetHashCode
public int GetHashCode(Product obj)
{
return obj.Id; // Id是不错的HashCode(类似定位床铺的宿舍号)
}


重新运行Main(),我的机器的结果是6毫秒。

static void Main(string[] args)
{
//...
Console.WriteLine("耗时:" + stopwatch.ElapsedMilliseconds); // 我的机器耗时6毫秒。
}

酷!
回复
leon51 2019-08-06
引用 2 楼 听雨停了 的回复:

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

            public string Name { get; set; }

            public Product(int id, string name)
            {
                this.Id = id;
                this.Name = name;
            }
        }

        public class ProductCompara : IEqualityComparer<Product>
        {
            public bool Equals(Product x, Product y)
            {
                bool result = true;
                if (x.Id!=y.Id || x.Name!=y.Name)
                {
                    result = false;
                }
                return result;
            }

            public int GetHashCode(Product obj)
            {
                return 0;
            }
        }



        static void Main(string[] args)
        {
            List<Product> list1 = new List<Product>() {
                  new Product(1,"A"),
                  new Product(2,"B"),
                  new Product(3,"A"),
            };
            List<Product> list2 = new List<Product>() {
                  new Product(1,"A"),
                  new Product(2,"C"),
                  new Product(3,"D"),
            };
            var list3 = list1.Except(list2, new ProductCompara());//移除list1中list2中存在的项
            var list4 = list2.Except(list1, new ProductCompara());//移除list2中list1中存在的项

        }
非常感谢你的回复,不过第26-29行看不懂,这个方法是否多余的?为何要传入参数obj而且始终返回0?
public int GetHashCode(Product obj)
{
	return 0;
}
回复
发动态
发帖子
C#
创建于2007-09-28

10.4w+

社区成员

.NET技术 C#
申请成为版主
社区公告

全世界最好的语言,没有之一.