views:

1577

answers:

4

Is there a way in Linq to do an OrderBy against a set of values (strings in this case) without knowing the order of the values?

Consider this data:

A
B
A
C
B
C
D
E

And these variables:

string firstPref, secondPref, thirdPref;

When the values are set like so:

firstPref = 'A';
secondPref = 'B';
thirdPref = 'C';

Is it possible to order the data like so:

A
A
B
B
C
C
D
E
A: 

Yes, you must implement your own IComparer<string> and then pass it in as the second argument of LINQ's OrderBy method.

An example can be found here: Ordering LINQ results

Ben Hoffstein
+8  A: 

If you put your preferences into a list, it might become easier.

List<String> data = new List<String> { "A","B","A","C","B","C","D","E" };
List<String> preferences = new List<String> { "A","B","C" };

IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.IndexOf(item));

This will put all items not appearing in preferences in front because IndexOf() returns -1. An ad hoc work around might be reversing preferences and order the result descending. This becomes quite ugly, but works.

IEnumerable<String> orderedData = data.OrderByDescending(
   item => Enumerable.Reverse(preferences).ToList().IndexOf(item));

The solution becomes a bit nicer if you concat preferences and data.

IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.Concat(data).ToList().IndexOf(item));

I don't like Concat() and ToList() in there. But for the moment I have no really good way around that. I am looking for a nice trick to turn the -1 of the first example into a big number.

Daniel Brückner
+1 nice move with that second list and indexOf.
James
Actually the case where IndexOf returns -1 can be solved by simply wrapping preferences.IndexOf(item) with Math.Abs(preferences.IndexOf(item)) and your solution works perfectly.
James
No, using Math.Abs mixes the "unpreferred" values with the second preference. Try with some data that is not mostly sorted to begin with.
Guffa
A: 

Danbrucs solution is more elegant, but here is a solution using a custom IComparer. This might be useful if you need more advanced conditions for your sort ordering.

    string[] svals = new string[] {"A", "B", "A", "C", "B", "C", "D", "E"};
    List<string> list = svals.OrderBy(a => a, new CustomComparer()).ToList();

    private class CustomComparer : IComparer<string>
    {
        private string firstPref = "A";
        private string secondPref = "B";
        private string thirdPref = "C";
        public int Compare(string x, string y)
        {
            // first pref 
            if (y == firstPref && x == firstPref)
                return 0;
            else if (x == firstPref && y != firstPref)
                return -1;
            else if (y == firstPref && x != firstPref)
                return 1;
            // second pref
            else if (y == secondPref && x == secondPref)
                return 0;
            else if (x == secondPref && y != secondPref)
                return -1;
            else if (y == secondPref && x != secondPref)
                return 1;
            // third pref
            else if (y == thirdPref && x == thirdPref)
                return 0;
            else if (x == thirdPref && y != thirdPref)
                return -1;
            else
                return string.Compare(x, y);
        }
    }
James
A: 

Put the preferred values in a dictionary. Looking up keys in a dictionary is a O(1) operation compared to finding values in a list which is a O(n) operation, so it scales much better.

Create a sort string for each preferred value so that they are placed before the other values. For the other values the value itself will be used as sorting string so that they are actually sorted. (Using any arbitrary high value would only place them at the end of the list unsorted).

List<string> data = new List<string> {
 "E", "B", "D", "A", "C", "B", "A", "C"
};
var preferences = new Dictionary<string, string> {
 { "A", " 01" },
 { "B", " 02" },
 { "C", " 03" }
};

string key;
IEnumerable<String> orderedData = data.OrderBy(
 item => preferences.TryGetValue(item, out key) ? key : item
);
Guffa