Chusux
Chusux

Reputation: 33

Merge 2 List of Objects, no duplicates, based on comparison

I need to merge 2 lists of objects, they share the same interface, but different concrete class.

The result should contain a List of new Objects based on each concrete class, and set a property on each object based on a comparison of the same objects from the lists.

class Program
{

    static void Main(string[] args)
    {
        List<IStuff> stuffList1 = new List<IStuff>();
        List<IStuff> stuffList2 = new List<IStuff>();

        stuffList1.Add(new Toaster
        {
            ValueOfMyThing = 1
        });
        stuffList1.Add(new Car
        {
            ValueOfMyThing = 3
        });
        stuffList1.Add(new Onion
        {
            ValueOfMyThing = 3
        });

        stuffList2.Add(new Toaster
        {
            ValueOfMyThing = 2
        });
        stuffList2.Add(new Car
        {
            ValueOfMyThing = 1
        });
        stuffList2.Add(new Onion
        {
            ValueOfMyThing = 5
        });

        List<IStuff> stuffList3 = new List<IStuff>();            

        // Need to merge stuffList1 and stuffList2 taking the stuff that has the higher valueOfMyThing
        // The result should be a stuffList3 with a Toaster 2 a Car 3 and an Onion 5
    }
}
interface IStuff
{
    int ValueOfMyThing { get; set; }
}

class Toaster: IStuff
{
    public int ValueOfMyThing { get; set; }
}
class Car : IStuff
{
    public int ValueOfMyThing { get; set; }
}
class Onion : IStuff
{
    public int ValueOfMyThing { get; set; }
}

Upvotes: 0

Views: 716

Answers (3)

Tim Schmelter
Tim Schmelter

Reputation: 460158

First you need the common property in the interface, otherwise you can't use polymoprhism to access it when you enumerate the items in the List<IStuff>:

interface IStuff
{
    int ValueOfMyThing { get; set; }
}

Now add this property to the classes as well (omitted).

Then you could use this LINQ query to group by the concrete type, for example Car, and get the item with the highest ValueOfMyThing for each group:

List<IStuff> stuffList3 = stuffList1.Concat(stuffList2)
    .GroupBy(x => x.GetType())
    .Select(g => g.OrderByDescending(x => x.ValueOfMyThing).First())
    .ToList();

This works, but I need stuffList3 to contain new Instances

Then you could provide a method to copy existing instances to new:

public interface IStuff
{
    int ValueOfMyThing { get; set; }
    IStuff Copy();
}

add it to your classes:

public class Toaster : IStuff
{
    public int ValueOfMyThing { get; set; }
    public IStuff Copy()
    {
        return new Toaster { ValueOfMyThing = ValueOfMyThing };
    }
}
// ...

and call Copy:

List<IStuff> stuffList3 = stuffList1.Concat(stuffList2)
    .GroupBy(x => x.GetType())
    .Select(g => g.OrderByDescending(x => x.ValueOfMyThing).First().Copy())
    .ToList();

Upvotes: 4

Chusux
Chusux

Reputation: 33

I needed to create new instance of the objects. So I ended up doing this: First as mentioned by Tim Schmelmer:

interface IStuff
{
    int ValueOfMyThing { get; set; }
}

class Toaster: IStuff
{
    public int ValueOfMyThing { get; set; }
}
class Car : IStuff
{
    public int ValueOfMyThing { get; set; }
}
class Onion : IStuff
{
    public int ValueOfMyThing { get; set; }
}

Then based on this answer Merge two Lists of different types

        List<IStuff> stuffList3 = new List<IStuff>();

        var joinedData = stuffList1.Join(stuffList2, sl1 => sl1.GetType(), sl2 => sl2.GetType(), (sl1, sl2) => new { sl1, sl2 });
        foreach (var pair in joinedData)
        {
            var newStuffValue = Math.Max(pair.sl1.ValueOfMyThing, pair.sl2.ValueOfMyThing);
            var newStuff = (IStuff)Activator.CreateInstance(pair.sl1.GetType());
            newStuff.ValueOfMyThing = newStuffValue;
            stuffList3.Add(newStuff);

        }

Now I´ve got a list of new concrete classes, but I´m not sure this is the best approach

Upvotes: 0

D-Shih
D-Shih

Reputation: 46229

Frist, you need to let valueOfMyThing property or field in IStuff interface be a contract for classes.

interface IStuff
{
    int valueOfMyThing { get; set; }
}

class Toaster : IStuff
{
    public int valueOfMyThing { get; set; }
}
class Car : IStuff
{
    public int valueOfMyThing { get; set; }
}
class Onion : IStuff
{
    public int valueOfMyThing { get; set; }
}

Then add this extension method to allow a Distinct Query with lambda parameters:

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this 
    IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    var seenKeys = new HashSet<TKey>();

    foreach (var element in source)
    {
        if (keySelector != null && seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

And finally use it on your list.

List<IStuff> stuffList3 = new List<IStuff>();

stuffList3.AddRange(stuffList2);
stuffList3.AddRange(stuffList1);

var r = stuffList3.DistinctBy(x => x.valueOfMyThing);

Upvotes: 0

Related Questions