This is a kinda tricky approach, but it gets the job done. I borrowed from the "Decorate-Sort-Undecorate" idiom in Python, which does sorting by associating a temporary sort key with an array, combined with the fun and useful fact that anonymous types in .NET have a default EqualityComparer that compares based on the values of their fields.
Step 1: Group the elements in each list by Name, and then associate an index with each item within each group, and flatten the groups back into a regular list:
var indexedAvailable = available.GroupBy(i => i.Name)
.SelectMany(g => g.Select((itm, idx) => new
{ Name = itm.Name, Index = idx }));
var indexedSelected = selected.GroupBy(i => i.Name)
.SelectMany(g => g.Select((itm, idx) => new
{ Name = itm.Name, Index = idx }));
This will turn the lists into these:
indexedAvailable indexedSelected
Name = Item 1, Index = 0 Name = Item 1, Index = 0
Name = Item 1, Index = 1 Name = Item 2, Index = 0
Name = Item 2, Index = 0 Name = Item 3, Index = 0
Name = Item 3, Index = 0
Name = Item 5, Index = 0
Now you can see that in the indexed lists, each numbered occurrence of any Name in available
will match only with the equivalent-numbered occurrence of the same name in selected
. So you can use a simple Except
to remove anything in indexedAvailable
that isn't in indexedSelected
, and then "undecorate' by turning the anonymous-typed indexed objects back into Item
s.
var selectable = indexedAvailable.Except(indexedSelected)
.Select(i => new Item() { Name = i.Name });
And the proof:
foreach (var item in selectable)
Console.WriteLine(item.Name);
//prints out:
//Item 1
//Item 5
Note that this will work even if selected
contains names that aren't in available
, e.g. if the second list has an Item 4
like in your last question.