简体   繁体   English

C# 从列表中删除重复对象并先增加

[英]C# Remove duplicated objects from list and increase first

I have an list of objects => class Example { int quantity; string name; string comment; }我有一个对象列表 => class Example { int quantity; string name; string comment; } class Example { int quantity; string name; string comment; } class Example { int quantity; string name; string comment; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment .我想删除所有重复class Example { int quantity; string name; string comment; }并通过具有相同namecomment的重复项的数量来增加quantity

Example:例子:

[
    {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"},
]

and the result it should be:结果应该是:

[
    {quantity: 2, name: "Hello", comment: "Hello there"},
    {quantity: 3, name: "Bye", comment: "Good bye"},
    {quantity: 1, name: "Hi", comment: "Hi there"}
]

I want to remove all duplicates我想删除所有重复项

You haven't defined when two example objects are "duplicates".您尚未定义两个示例对象何时“重复”。 I guess, that you mean to say, that if two Examples objects have the same values for properties Name and Comment , then they are duplicates.我猜,你的意思是说,如果两个 Examples 对象的属性NameComment具有相同的值,那么它们就是重复的。

Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates.通常,您可以使用Enumerable.GroupBy的重载之一来查找重复项。 Use the overload with a parameter resultSelector to define exactly what you want as result.使用带有参数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(),
    });

This will only work if you want exact string equality.这仅在您想要精确的字符串相等时才有效。 Are "Hello" and "HELLO" equal? “你好”和“你好”是否相等? And how about "Déjà vu" and "Deja vu"?那么“Déjà vu”和“Deja vu”呢? Do you want case insensitivity for Name and Comment?您想要名称和评论不区分大小写吗? And how about diacritical characters?那么变音字符呢?

If you want more than just plain string equality, consider to create an ExampleComparer class.如果您想要的不仅仅是简单的字符串相等,请考虑创建一个 ExampleComparer class。

class ExampleComparer : EqualityComparer<Example>
{
    ... // TODO: implement
}

Usage would be:用法是:

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);

Implement the ExampleComparer实现 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;

I chose for two separate string comparers, so if later you decide different comparison, for instance Name has to match exactly, then you only have to change it here.我选择了两个单独的字符串比较器,所以如果稍后您决定不同的比较,例如名称必须完全匹配,那么您只需在此处更改它。

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);
}

Note: this will also work if Name or Comment are null or empty!注意:如果名称或评论是 null 或为空,这也将起作用!

I used operator ^ (XOR) , because that gives a fairly good hash if there are only two fields to consider.我使用了operator ^ (XOR) ,因为如果只有两个字段需要考虑,这会给出一个相当好的 hash 。 If you think that the vast majority of Examples have unique names, consider to check only property Name:如果您认为绝大多数示例具有唯一名称,请考虑仅检查属性名称:

return NameComparer.GetHashCode(x.Name);

Because method Equals uses the NameComparer and CommentComparer to check for equality, make sure you use the same Comparers to calculate the HashCode.因为方法Equals使用 NameComparer 和 CommentComparer 来检查相等性,所以请确保使用相同的比较器来计算 HashCode。

Here's a simple solution, which gives you the answer in the List tata but you can do.ToArray() if you wish.这是一个简单的解决方案,它可以在 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);
        }
    }

A good exercise would be to LINQ that! LINQ 是一个很好的练习!

Here's what I'd do:这是我要做的:

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();

That gives:这给出了:

后

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM