6

I have a List of key value pairs which I would like to sort according to an order specified in another array.

var requiredOrder = new String[] { "PH", "HH", "PR", "SR", "UN", "UD", "WD", "WE", "OT" };


        var listToBeSorted = new List<KeyValuePair<string, string>>() {
            new KeyValuePair<string, string>("A","PR"),
            new KeyValuePair<string, string>("B","PH"),
            new KeyValuePair<string, string>("C","HH"),
            new KeyValuePair<string, string>("D","WD"),
            new KeyValuePair<string, string>("E","OT"),
            new KeyValuePair<string, string>("F","UN"),
            .....
        };

'listToBeSorted' is a really a collection I fetch for a dropdownlist that needs to be sorted by value according to the 'requiredOrder' array.

  • Can the list to be sorted have duplicates? For example 2 pairs with PR as second element – Attersson Jun 19 '18 at 09:51
  • 1
    No. The values are unique. Sorry I've ammended my code snippet to avoid confusion –  Jun 19 '18 at 09:52
  • Ahh great. Then the sort is not needed since it would be N^2LogN (index of, times sort). Better loop requiredOrder and find within list (if exists) and push to result. N^2... (I can't write an answer right now) – Attersson Jun 19 '18 at 09:54
  • If you can convert `listToBeSorted` to an array you can use [`Array.Sort(Array keys, Array items)`](https://msdn.microsoft.com/en-us/library/85y6y2d3(v=vs.110).aspx) – Matthew Watson Jun 19 '18 at 09:57
  • 1
    you can loop over the first array and get the code (such as PH) and change the index of it in the second array to match the index in the first one, got me? – Plexis Plexis Jun 19 '18 at 09:58
  • @PlexisPlexis not sure this can be done with a List but it would yield a O(N) re-sort algorithm. Great! This gives me offtopic ideas about re-sorting with maps – Attersson Jun 19 '18 at 10:01
  • @fubo yeah so change `List.IndexOf()` to `Array.IndexOf()`, the remainder remains the same. – CodeCaster Jun 19 '18 at 10:05
  • Why sort anything at all? Put the first list into a lookup table and just loop over the one with the keys and extract the right object from the lookup table? – Lasse V. Karlsen Jun 19 '18 at 10:10
  • @Attersson - not clear why you think looping over the other array is any better, it just seems to add an O(n) scan. – bommelding Jun 19 '18 at 10:11

5 Answers5

7

You can sort by the index of the item in the requiredOrder array by using Array.IndexOf()

listToBeSorted = listToBeSorted.OrderBy(x => Array.IndexOf(requiredOrder, x.Value)).ToList();
fubo
  • 44,811
  • 17
  • 103
  • 137
2

Considered that the list to be sorted has only uniques:

The O(N^2) approach will perform better than OrderBy with IndexOf, the latter being NLogN * N = (N^2)LogN (since the indexof, which hides N, performed at each step of the sort), see comment within the code

var sortedList = new List<KeyValuePair<string, string>>();

for (int i = 0; i < requiredOrder.Length; i++) { // "i" spares indexof.
    for (int j = 0, l = listToBeSorted.Count; j < l; j++) {
        if (requiredOrder[i].Equals(listToBeSorted[j].Value)) {
            sortedList.Add(listToBeSorted[j]);
            //might as well Remove from listToBeSorted to decrease complexity.
            //listToBeSorted.RemoveAt(j);
            break;
        }
    }
}

Not the classiest of code, but can be further improved.

If you Remove from listToBeSorted (and adjust loop indexes) complexity will tend to NLogN (to be accurate, still 1/2 N^2) and space complexity to "0 additional".

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
Attersson
  • 4,755
  • 1
  • 15
  • 29
2

I loved Lasse's answer, but it can be even a bit neater. Try this:

var requiredOrder = new String[] { "PH", "HH", "PR", "SR", "UN", "UD", "WD", "WE", "OT" };

var listToBeSorted = new List<KeyValuePair<string, string>>()
{
    new KeyValuePair<string, string>("A","PR"),
    new KeyValuePair<string, string>("B","PH"),
    new KeyValuePair<string, string>("C","HH"),
    new KeyValuePair<string, string>("D","WD"),
    new KeyValuePair<string, string>("E","OT"),
    new KeyValuePair<string, string>("F","UN"),
};

var lookup = listToBeSorted.ToLookup(x => x.Value);

var sorted =
    from x in requiredOrder
    from y in lookup[x]
    select y;

That gives:

sorted

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
1

I don't see the need for any sorting at all.

var requiredOrder = new String[] { "PH", "HH", "PR", "SR", "UN", "UD", "WD", "WE", "OT" };

var listToBeSorted = new List<KeyValuePair<string, string>>() {
    new KeyValuePair<string, string>("A","PR"),
    new KeyValuePair<string, string>("B","PH"),
    new KeyValuePair<string, string>("C","HH"),
    new KeyValuePair<string, string>("D","WD"),
    new KeyValuePair<string, string>("E","OT"),
    new KeyValuePair<string, string>("F","UN"),
    .....
};

Simply transform the listToBeSorted into a lookup table, then grab all the objects from this table when iterating the requiredOrder:

var lookup = listToBeSorted.ToLookup(kvp => kvp.Value);
var result =
    from key in requiredOrder
    from obj in lookup[key]
    select obj;
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
0

Here's another approach:

  1. Create an array of indices the same length as the array to be sorted.
  2. Use Array.Sort(Array keys, Array items) to sort the indices into the correct order.
  3. Use the index array to access the list in the desired order.

Here's the sample code - the lines corresponding to the steps in the algorithm above are annotated with (1), (2) and (3):

using System;
using System.Collections.Generic;
using System.Linq;

namespace Demo
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var requiredOrder = new [] { "PH", "HH", "PR", "SR", "UN", "UD", "WD", "WE", "OT" };

            var listToBeSorted = new List<KeyValuePair<string, string>>() {
                new KeyValuePair<string, string>("A", "PR"),
                new KeyValuePair<string, string>("B", "PH"),
                new KeyValuePair<string, string>("C", "HH"),
                new KeyValuePair<string, string>("D", "WD"),
                new KeyValuePair<string, string>("E", "OT"),
                new KeyValuePair<string, string>("F", "UN"),
                new KeyValuePair<string, string>("G", "UD"),
                new KeyValuePair<string, string>("H", "WE"),
                new KeyValuePair<string, string>("I", "SR")
            };

            int[] indices = Enumerable.Range(0, requiredOrder.Length).ToArray(); // (1)

            Array.Sort(requiredOrder, indices); // (2)

            foreach (var index in indices) // (3)
            {
                Console.WriteLine(listToBeSorted[index].Key);
            }
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276