bizzehdee
bizzehdee

Reputation: 20993

Merging duplicate elements within an IEnumerable

I currently have an IEnumerable<MyObject> where MyObject has the properties String Name and long Value.

If i was to have within the Enumerable, 10 instances of MyObject, each with a different name and value, with the exception of one having the same name as the other.

Does .NET (or LINQ) have a built in method which will allow me to find the duplicate, and if possible, merge the Value property so that there ends up being only 9 elements within the enumerable, each with a distinct Name and the one that had a duplicate has the Value that is equal to the sum of its self and the duplicate.

So far i have found that the only way to iterate over the entire IEnumerable and look for the duplicates and generate a new IEnumerable of unique items, but this seems untidy and slow.

Upvotes: 11

Views: 2803

Answers (4)

Vladimir
Vladimir

Reputation: 7475

list.GroupBy(e => e.Name).Select(group => new MyObject
    {
        Name = group.Key,
        Value = group.Sum(e => e.Value)
    }
)

Update:
Another variant:

list.GroupBy(
    e => e.Name,
    e => e,
    (name, group) => group.Aggregate((result, e) =>
        {
            result.Value += e.Value;
            return result;
        }
    )
)

Upvotes: 8

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236188

You can group items by name and project results to 'merged' objects:

objects.GroupBy(o => o.Name)
       .Select(g => new MyObject { Name = g.Key, Value = g.Sum(o => o.Value) });

UPDATE: Another option, if new MyObject instantiation is undesired (e.g. you have many properties in this class, or you should preserver references) then you can use aggregation with first item in group as accumulator:

objects.GroupBy(o => o.Name)
       .Select(g => g.Skip(1).Aggregate(
                        g.First(), (a, o) => { a.Value += o.Value; return a; }));

Upvotes: 17

Mohammad Mirmostafa
Mohammad Mirmostafa

Reputation: 1775

Implement interface IEquatable and use Ditinct method. As follow:

internal class Program
{
    private static void Main(string[] args)
    {
        var items = new List<MyClass>
                    {
                        new MyClass
                        {
                            Name = "Name1",
                            Value = 50
                        },
                        new MyClass
                        {
                            Name = "Name2",
                            Value = 20
                        },
                        new MyClass
                        {
                            Name = "Name3",
                            Value = 50
                        }
                    };
        var distinct = items.Distinct().ToList();
    }
}

internal class MyClass : **IEquatable<MyClass>**
{
    public String Name { get; set; }
    public int Value { get; set; }

    **public bool Equals(MyClass other)
    {
        if (ReferenceEquals(null, other))
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return this.Value == other.Value;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj))
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj.GetType() != this.GetType())
            return false;
        return this.Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        return this.Value;
    }

    public static bool operator ==(MyClass left, MyClass right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(MyClass left, MyClass right)
    {
        return !Equals(left, right);
    }**
}

Upvotes: 1

undefined
undefined

Reputation: 34238

I dont know a single method solution but what about:

set.GroupBy(g=>g.Name).Select(g=> new MyObject{Name=g.Key, Value=g.Sum(i=>i.Value)});

Upvotes: 3

Related Questions