C#从列表中删除重复的对象并首先增加
Posted
技术标签:
【中文标题】C#从列表中删除重复的对象并首先增加【英文标题】:C# Remove duplicated objects from list and increase first 【发布时间】:2022-01-09 09:18:34 【问题描述】:我有一个对象列表 => class Example int quantity; string name; string comment;
,我想删除所有重复项并将 quantity
增加具有相同 name
和 comment
的重复项的数量。
例子:
[
quantity: 1, name: "Hello", comment: "Hello there",
quantity: 2, name: "Bye", comment: "Good bye",
quantity: 1, name: "Hi", comment: "Hi there",
quantity: 1, name: "Hello", comment: "Hello there",
quantity: 1, name: "Bye", comment: "Good bye",
]
结果应该是:
[
quantity: 2, name: "Hello", comment: "Hello there",
quantity: 3, name: "Bye", comment: "Good bye",
quantity: 1, name: "Hi", comment: "Hi there"
]
【问题讨论】:
你尝试了什么,结果如何? GroupBy 名称/注释组合,然后选择组键属性和数量的 Sum()。 请注意,您的示例不是 C#。这是一个 json 问题,还是只是一个例子? 是的,只是一个例子。 【参考方案1】:我想删除所有重复项
您尚未定义两个示例对象何时“重复”。我猜,您的意思是,如果两个示例对象的属性Name
和Comment
具有相同的值,那么它们就是重复的。
通常您可以使用Enumerable.GroupBy 的重载之一来查找重复项。使用带有参数resultSelector
的重载来准确定义您想要的结果。
IEnumerable<Example> examples = ...
var result = examples.GroupBy(
// key: Name-Comment
example => new
Name = example.Name,
Comment = example.Comment,
// parameter resultSelector: for every Name/Comment combination and all
// Examples with this Name/Comment combination make one new example:
(nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
Name = nameCommentCombination.Name,
Comment = nameCommentCombination.Comment,
// Quantity is the sum of all Quantities of all Examples with this
// Name/Comment combination
Quantity = examplesWithThisNameCommentCombination
.Select(example => example.Quantity)
.Sum(),
);
这仅在您想要精确的字符串相等时才有效。 “你好”和“你好”是否相等?那么“Déjà vu”和“Deja vu”呢?您想要名称和评论不区分大小写吗?那么变音字符呢?
如果您想要的不仅仅是简单的字符串相等,请考虑创建一个 ExampleComparer 类。
class ExampleComparer : EqualityComparer<Example>
... // TODO: implement
用法如下:
IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...
var result = examples.GroupBy(example => example, // key
// resultSelector:
(key, examplesWithThisKey) => new Example
Name = key.Name,
Comment = key.Comment,
Quantity = examplesWithThiskey.Sum(example => example.Quantity),
,
comparer);
实现 ExampleComparer
class ExampleComparer : EqualityComparer<Example>
public static IEqualityComparer<Example> ByNameComment get; = new ExampleComparer;
private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;
我选择了两个单独的字符串比较器,所以如果以后你决定不同的比较,例如名称必须完全匹配,那么你只需要在这里更改它。
public override bool Equals (Example x, Example y)
// almost every Equals method starts with the following three lines
if (x == null) return y == null; // true if both null
if (y == null) return false; // false, because x not null
if (Object.ReferenceEquals(x, y)) return true; // same object
// return true if both examples are considered equal:
return NameComparer.Equals(x.Name, y.Name)
&& CommentComparer.Equals(x.Comment, y.Comment);
public override int GetHashCode(Example x)
if (x == null) return 5447125; // just a number
return NameComparer.GetHashCode(x.Name)
^ CommentComparer.GetHashCode(x.Comment);
注意:如果 Name 或 Comment 为 null 或为空,这也将起作用!
我使用了operator ^ (XOR)
,因为如果只有两个字段需要考虑,它会提供相当好的哈希值。如果您认为绝大多数示例具有唯一名称,请考虑仅检查属性名称:
return NameComparer.GetHashCode(x.Name);
因为方法 Equals
使用 NameComparer 和 CommentComparer 来检查相等性,所以请确保使用相同的 Comparer 来计算 HashCode。
【讨论】:
实用工具:HashCode.Combine【参考方案2】:这是我要做的:
Example[] before = new Example[]
new Example Quantity = 1, Name = "Hello", Comment = "Hello there" ,
new Example Quantity = 2, Name = "Bye", Comment = "Good bye" ,
new Example Quantity = 1, Name = "Hi", Comment = "Hi there" ,
new Example Quantity = 1, Name = "Hello", Comment = "Hello there" ,
new Example Quantity = 1, Name = "Bye", Comment = "Good bye" ,
;
Example[] after =
before
.GroupBy(x => new x.Name, x.Comment , x => x.Quantity)
.Select(x => new Example Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment )
.ToArray();
这给了:
也许这个版本更清晰一点:
Example[] after =
before
.GroupBy(
x => new x.Name, x.Comment ,
(k, xs) => new Example
Quantity = xs.Sum(x => x.Quantity),
Name = k.Name,
Comment = k.Comment
)
.ToArray();
或者这样:
Example[] after =
(
from x in before
group x.Quantity by new x.Name, x.Comment into xs
select new Example
Quantity = xs.Sum(x => x),
Name = xs.Key.Name,
Comment = xs.Key.Comment
).ToArray();
【讨论】:
太棒了,你能不能在组部分解释一下, f => f.Quantity
不是这个级别的键的一部分,它在列表/数组中,在选择部分Quantity = x.Sum()
它是怎么知道的数量总和
.GroupBy(x => new x.Name, x.Comment , x => x.Quantity)
与 group x.Quantity by new x.Name, x.Comment
相同。所以每个组只是@987654330@ 的可枚举,因此x.Sum()
有效。【参考方案3】:
这是一个简单的解决方案,它在 List tata 中为您提供答案,但您可以根据需要执行 .ToArray()。
public class Example
public int quantity;
public string name;
public string comment;
Example[] toto = new Example[]
new Example
quantity = 1,
name = "Hello",
comment = "Hello there"
,
new Example
quantity = 2,
name = "Bye",
comment = "Good bye"
,
new Example
quantity = 1,
name = "Hi",
comment = "Hi there"
,
new Example
quantity = 1,
name = "Hello",
comment = "Hello there"
,
new Example
quantity = 1,
name = "Bye",
comment = "Good bye"
;
List<Example> tata = new List<Example>();
foreach (Example exa in toto)
bool found = false;
foreach (Example exb in tata)
if (exb.name == exa.name && exb.comment == exa.comment)
exb.quantity += exa.quantity;
found = true;
break;
if (!found)
tata.Add(exa);
LINQ 是一个很好的练习!
【讨论】:
Euww,嵌套循环.. 字典请 @CaiusJard 嵌套循环有什么问题?你能详细说明一下吗?你会如何处理字典?我们这里有三个条目,而不是两个。 嵌套循环有什么问题 - 接近 O(n²).. 你会如何处理字典? - 将键设为 @ 987654322@或(name, comment)
或记录; AT、ValueTuples 和记录在其所有组成道具的平等/散列中建立了
@CaiusJard 谢谢!在内部,不是所有的解决方案都等同于 2 个嵌套循环吗?我的意思是,做 LINQ 或 DICT 需要时间,不是吗?
这可能取决于,但通常答案是“否”。当列表查找某些内容时,它会从头开始检查每个项目。当字典找到某物时,它会计算它期望该项目在哪里,然后去那里查看该项目;如果它不是正确的,则有一个指向另一项的指针(您的公主在另一座城堡中),这可能是正确的。如果它们是食谱书,那么列表就像“一页一页地寻找双层巧克力蛋糕,在 96 次尝试失败后在第 97 页找到它”,而字典就像..以上是关于C#从列表中删除重复的对象并首先增加的主要内容,如果未能解决你的问题,请参考以下文章
从一个列表中删除元素如果不包含在另一个列表中。 C# [重复]