Here is a specific implementation of the method body of @nannanas's answer, which also works when listA
does not contain all letters present in listB
:
listB = listA.Union(listB.Select(entry => entry.Letter))
.Join(listB,
letter => letter,
sample => sample.Letter,
( _, sample ) => sample)
.ToList();
Example fiddle here.
Walk-through
Let's say we have the following lists:
var listA = new List<string> { "B", "C", "A" };
var listB = new List<Sample>
{
new Sample { Letter = "E", Number = "1" },
new Sample { Letter = "C", Number = "2" },
new Sample { Letter = "A", Number = "3" },
new Sample { Letter = "B", Number = "4" },
new Sample { Letter = "D", Number = "5" }
};
The first line:
listB.Select(entry => entry.Letter)
will extract each Letter
value in listB
, resulting in:
{ "E", "C", "A", "B", "D" }
listA.Union(listB.Select(entry => entry.Letter))
uses .Union() to create the set union of listA
and listB
's Letter
values by first yielding each distinct item in the set of listA
("B", "C", "A"
), then yielding each distinct item in the set of Letter
s from listB
that are not present in listA
("E", "D"
); resulting in:
{ "B", "C", "A", "E", "D" }
We now have the order of the letters that we want listB
to reflect: First, the order given by listA
, then, the remaning letters present in listB
.
The .Join() operation
.Join() associates an outer sequence with an inner sequence based on association keys defined by a key selector for each sequence; and then returns an IEnumerable<T>
from the associations based on the result selector:
outerSequence.Join(innerSequence,
outerItem => /* */, // key selector (outerItem is a string)
innerItem => /* */, // key selector (innerItem is a Sample)
( associatedOuterItem, associatedInnerItem ) => /* */) // result selector
{ "B", "C", "A", "E", "D" }
is thereby the outer sequence in the .Join()
operation. The inner sequence of the .Join()
operation is listB
. By using .Join()
, we can associate items in the outer sequence with items in the inner sequence by the sequences' matching key selectors.
For the outer sequence, we define the key selector as being the whole item:
letter => letter // alternatively: outerItem => outerItem
As an example, the two first items in the outer sequence are "B"
and "C"
; so, the association key for the first two items in the outer sequence are "B"
and "C"
.
For the inner sequence, we define the key selector as being the Letter
property of the Sample
object:
sample => sample.Letter // alternatively: innerItem => innerItem.Letter
As an example, the two first items in the inner sequence are Sample { Letter = "E", Number = "1" }
and Sample { Letter = "C", Number = "2" }
; so, the association key for the first two items in the inner sequence are each item's Letter
value: "E"
and "C"
.
Hence, the full set of key selectors for the outer and inner sequences are, respectively:
{ "B", "C", "A", "E", "D" }
{ "E", "C", "A", "B", "D" }
The .Join()
operation now associates the items in each sequence based on the key selectors. The associations keep the order provided by the outer sequence, and can be illustrated as follows:
Item from outer sequence |
Item from inner sequence |
"B" |
Sample { Letter = "B", Number = "4" } |
"C" |
Sample { Letter = "C", Number = "2" } |
"A" |
Sample { Letter = "A", Number = "3" } |
"E" |
Sample { Letter = "E", Number = "1" } |
"D" |
Sample { Letter = "D", Number = "5" } |
The last .Join()
input parameter is the result selector, which defines what should be returned from each association. Seeing as we only need the item from the inner sequence, we specify that in the result selector:
( _, sample ) => sample
(The result selector could have been rewritten to e.g. ( letter, sample ) => sample
, but seeing as the items from the outer sequence (referred to as letter
) are not of interest, I have simply discarded (_
) that object in my example.)