2

Let's imagine we have a list of strings var listA = new List<string> {"B", "C", "A"}, and it is sorted in the desired manner. And, there is an object defined like:

public class Sample {
   public string Letter {get;set;} // "A","B","C","D","#"
   public string Number {get;set;} // not relevant
}

Also, there is another list, List<Sample> listB, and I want to sort it in a way that first comes objects with Letter values "B", then "C" and on the end, "A", so to respect same order as it is in the listA. Any ideas? :)

EDIT: Letter property can have a wider range of characters, unlike listA which can have just those 3. Expected order "B","C", "A"..."all the rest, order not relevant"

zlaayaa
  • 585
  • 2
  • 6
  • 25

4 Answers4

2

Assume value of Letter are within listA. It's worth considering redesigning the algorithm if the assumption is false. And it may depend on requirement of expected order the out of range ones.

   var sortedSamples = listB.OrderBy(x=>listA.IndexOf(x.Letter));

For better performance, cache the orders into a dictionary:

   var orders = new Dictionary<string, int>();
   for (int i = 0; i < listA.Count; i++)
   {
      orders.Add(listA[i], i);
   }
   var sortedSamples = listB.OrderBy(x=>orders[x.Letter]);

If possible values range of Letter is much smaller than listA, then better consturct orders like this:

   foreach (string letter in listB.Select(x=>x.Letter))
   {
      orders.Add(letter, listA.IndexOf(letter));
   }

Update

If letters' range are out of listA, and since OP want the out ones to be at the end:

   var orders = new Dictionary<string, int>();
   for (int i = 0; i < listA.Count; i++)
   {
      orders.Add(listA[i], i);
   }
   int lastIndex = listA.Count;
   foreach (string letter in listB.Select(x=>x.Letter)
                                  .Distinct().Except(listA))
   {
      orders.Add(letter, lastIndex);
   }
Lei Yang
  • 3,970
  • 6
  • 38
  • 59
2

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 Letters 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.)

Astrid E.
  • 2,280
  • 2
  • 6
  • 17
1

You should using something like this

            var listA = new List<string> { "B", "C", "A" };
            var listB = new List<string> { "A", "B", "C" };
            listB = listB.OrderBy(d => listA.IndexOf(d)).ToList();
            // listB: "B", "C", "A"

        
jæk
  • 114
  • 9
0

You could use this extension which also supports comparing the entries in the lists by a specific property (e.g. id instead of reference)

public static IEnumerable<TFirst> OrderBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TKey> firstKeySelector, Func<TSecond, TKey> secondKeySelector) =>
            second
                .Join(
                    first,
                    secondKeySelector,
                    firstKeySelector,
                    (_, firstItem) => firstItem);

this will sort the items in second the same way as they are already sorted in first comparing them using both key selectors.

Be aware that this only works if ALL the keys in first are also present in second.

Nannanas
  • 591
  • 2
  • 8