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 增加具有相同 namecomment 的重复项的数量。

例子:

[
    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】:

我想删除所有重复项

您尚未定义两个示例对象何时“重复”。我猜,您的意思是,如果两个示例对象的属性NameComment 具有相同的值,那么它们就是重复的。

通常您可以使用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 =&gt; f.Quantity 不是这个级别的键的一部分,它在列表/数组中,在选择部分Quantity = x.Sum()它是怎么知道的数量总和 .GroupBy(x =&gt; new x.Name, x.Comment , x =&gt; 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#)

从一个列表中删除元素如果不包含在另一个列表中。 C# [重复]

list.remove(x) 删除超过此列表中的对象[重复]

使用 Python 删除对象列表中的重复项

在合并包含列表时从包含列表的列表对象中删除重复项

如果某些陈述为真,则从列表中删除对象[重复]