zman
zman

Reputation: 353

Linq, select differences between two lists, select same between same list, and select duplicates same list

I want to know if it's possible in LINQ to achieve something along the lines of:

newList: { [1], [2] }
oldList: { [2], [3], [4], [5] }
resultantList = { [1], [2, 2], [3], [4], [5] }

Ok that was an oversimplification. Lets say:

Class A
{
    public string Name;
    public IList<B> Items;
    public bool Equals(A obj)
    {
         return obj.Name == Name;
    }
}


newList<A> = {{ Name = "A", Items[1]}, { Name = "B", Items[1] }}
oldList<A> = {{ Name = "C", Items[2]}, { Name = "A", Items[2] }, { Name = "D", Items[1] }, { Name = "E", Items[1] },}
mergedList<A> = {{ Name = "A", Items[3]}, { Name = "B", Items[1]}, { Name = "C", Items[2]}, { Name = "D" , Items[1]}, { Name = "E" , Items[1]}}

Notice that for the instance with Name="A" the list is actually the merged list of both lists. Order doesn't matter and in reality the equality is more complex.

I wish to achieve this on types (i.e. below works, but would be inefficient):

var fragments = newGeometryFragments.Except(oldGeometryFragments).ToList();
fragments.AddRange(oldGeometryFragments.Except(newGeometryFragments).ToArray());
var mergedFragments = (from newGeometry in newGeometryFragments
                       from oldGeometry in oldGeometryFragments
                       where newGeometry.Equals(oldGeometry)
                       select MergeGeometryFragments(newGeometry, oldGeometry)).ToArray();
 fragments.AddRange(mergedFragments);

Upvotes: 1

Views: 1949

Answers (2)

stackh34p
stackh34p

Reputation: 9009

You can use the following code, assuming you have the oldList and newList already filled:

newList.Union(oldList) // unite the collections
   .GroupBy(x => x /*, ComparerInstance*/) // will group by unique elemens
   .Select(x => x.ToList()) // or .ToArray(), convert each group to array or list
   .ToList(); // return a list of lists/arrays

The code above will produce a list of collections. Each collection will have one or more equal elements, depending on how many times that element is present in newList.Union(oldList).

If you are using specific elements and want to control the way they are compared (determined equal), pass a custom IEqualityComparer<YourType> instance to the GroupBy method.

Upvotes: 1

Tim Schmelter
Tim Schmelter

Reputation: 460208

You could use an outer join:

var oldList = new List<int?> { 2, 3, 4, 5 };
var newList = new List<int?> { 1, 2 };
var result = from all in oldList.Union(newList).OrderBy(num => num)
             join o in oldList on all equals o into gjOld
             from oldOuter in gjOld.DefaultIfEmpty()
             join n in newList on all equals n into gjNew
             from newOuter in gjNew.DefaultIfEmpty()
             select new { newVal = newOuter, oldVal = oldOuter };

This works also with a custom class when you implement Equals and GetHashCode or implement a custom IEqualityComparer<T>. Then the Nullable<int> trick is also unnecessary

Demo

newVal: 1   oldVal: 
newVal: 2   oldVal: 2
newVal:     oldVal: 3
newVal:     oldVal: 4
newVal:     oldVal: 5

Upvotes: 0

Related Questions