Combine two lists into one based on a property

I would like to ask if there is an elegant and efficient way to combine two MyClass lists into one?

MyClass looks like this:

  • ID: int
  • Title: string
  • ExtID: int?

and the lists are populated from different sources, and the objects in the lists have a common identifier, so it looks like this:

 MyClass instance from List1 ID = someInt Name = someString ExtID = null 

And an instance of MyClass from List2

 ID = someInt (same as List1) Name = someString (same as List1) ExtID = someInt 

Basically I need to combine these two lists, so the result will be a list containing:

 ID = someInt (from List1) Name = someString (from List1) ExtID = someInt (null if no corresponding item - based on ID - on List2) 

I know that I can do this simply using the foreach loop, but I would like to know if there is a more elegant and possibly preferred (due to performance, readability) method?

+5
source share
3 answers

There are many approaches depending on what is the priority, for example. Union + Lookup:

 //this will create a key value pairs: id -> matching instances var idMap = list1.Union(list2).ToLookup(myClass => myClass.ID); //now just select for each ID the instance you want, ex. with some value var mergedInstances = idMap.Select(row => row.FirstOrDefault(myClass => myClass.ExtId.HasValue) ?? row.First()); 

The advantage above is that it will work with any number of all lists, even if they contain many duplicate isntances, and then you can easily change the merge conditions

A slight improvement would be to retrieve a method to merge instances:

 MyClass MergeInstances(IEnumerable<MyClass> instances){ return instances.FirstOrDefault(myClass => myClass.ExtId.HasValue) ?? instances.First(); //or whatever else you imagine } 

and now just use it in the code above

  var mergedInstances = idMap.Select(MergeInstances); 

Clean, flexible, simple, no additional conditions. Performance is not perfect, but who cares.

Edit: since performance is a priority, a few more options

  • Do a search as shown above, but only for a smaller list. Then iterate through more and make the necessary changes O (m log m) + O (n). m - the size of the smaller list, n - the size of the list is larger - should be the fastest.

  • Order both lists by item IDs. Create a for loop that iterates through both of them, keeping the current index for the item with the same identifier for both lists. Move the pointer to the next smallest identifier found in both lists, if there is one, move only that. O (n log n) + O (m log m) + O (n);

+1
source

Is this what you want

 var joined = from Item1 in list1 join Item2 in list2 on Item1.Id equals Item2.Id // join on some property select new MyClass(Item1.Id, Item1.Name, Item1.ExtID??Item2.ExtID); 

Edit: if you are looking for an outer join,

 var query = from Item1 in list1 join Item2 in list2 on Item1.Id equals Item2.Id into gj from sublist2 in gj.DefaultIfEmpty() select new MyClass(Item1.Id, Item1.Name, sublist2??string.empty); 

Ease of reading, using a foreach loop is not a bad idea.

+1
source

I would like to create a foreach loop in a method of this class, so every time you needed to do such a thing, you would use something like

 instanceList1.MergeLists(instanceList2) 

and with this method you can control whatever you want with the help of the merge operation.

-1
source

Source: https://habr.com/ru/post/1235182/


All Articles