简体   繁体   中英

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; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment .

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.

Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates. Use the overload with a parameter resultSelector to define exactly what you want as result.

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"? 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.

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

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!

I used operator ^ (XOR) , because that gives a fairly good hash if there are only two fields to consider. 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.

Here's a simple solution, which gives you the answer in the List tata but you can do.ToArray() if you wish.

    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!

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:

后

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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