Home > Blockchain >  C# LINQ OrderBy values from another List(IEnumerable)
C# LINQ OrderBy values from another List(IEnumerable)

Time:02-24

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"

CodePudding user response:

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);
   }

CodePudding user response:

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"

        

CodePudding user response:

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.Concat(listB.Select(entry => entry.Letter)).Distinct()
    .Join(listB,
         letter => letter,
         sample => sample.Letter,
         ( _, sample ) => sample)
    .ToList();

Explanation:

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, and thereby result in:
{ "E", "C", "A", "B", "D" }.

listB = listA.Concat(listB.Select(entry => entry.Letter)) will then result in the contents of listA followed by the extracted letters from listB:
{ "B", "C", "A", "E", "C", "A", "B", "D" }

By calling .Distinct(), any duplicates are removed, and we are left with:
{ "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.

TODO: Further explanations when I have the time.


Example fiddle here.

CodePudding user response:

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.

  • Related