-1

This refers to

Sort a list by a custom order

I will go for a similar example... I have a dictionary containing users

public class UserInfo
{
    public string Name { get; set; }

    public string Location { get; set; }
}

the data:

private Dictionary<string, UserInfo> users = new Dictionary<string, UserInfo>(); // id + user

and a list of possible locations

        private string[] locations = {
            "europe",
            "america",
            "asia",
            "africa"
        };

how can I order this dictionary by these locations?

There should be the output

// ... all europeans

// ... all americans

// ... all asians

// ... all africans

when calling

    Dictionary<string, UserInfo> sortedUsers = ; // Sorting the dict "users" by locations

    foreach (KeyValuePair<string, UserInfo> info in sortedUsers)
    {
        Console.WriteLine("L: " + info.Value.Location + " N: " + info.Value.Name);
    }
peterHasemann
  • 1,550
  • 4
  • 24
  • 56

5 Answers5

2

A dictionary isn't an ordered collection, but it seems you want it's Values to be ordered:

var orderedUsersByLocation = users.Values.OrderBy(ui => ui.Location);

If you want a List<UserInfo> use ToList:

List<UserInfo> result = orderedUsersByLocation.ToList();

Maybe i didn't get the meaning of the string[] of possible locations, do you mean this array specifies the order and those locations are the possible locations in the UserInfo.Location?

private string[] locations = {
        "europe",
        "america",
        "asia",
        "africa"
    };

var orderedUsersByLocation = users.Values
    .OrderBy(ui => Array.IndexOf(locations, ui.Location));

If it's possible that a location is not contained in that array Array.IndexOf returns -1, so those entries will be first. If that's possible and you want them to be listed last:

var orderedUsersByLocation = users.Values
    .Select(ui => new { UserInfo = ui, Index = Array.IndexOf(locations, ui.Location)})
    .OrderBy(x => x.Index == - 1 ? int.MaxValue : x.Index)
    .Select(x => x.UserInfo);
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • This will sort in alphabetical order. Not sure it's what he wants. He wants to sort depending `locations` values – GGO Nov 15 '17 at 13:41
  • You mean the locations are sorted alphabetical? – peterHasemann Nov 15 '17 at 13:47
  • Yes in the Tim example, `users.Values.OrderBy(ui => ui.Location);` will sort by `Location` property alphabetical order – GGO Nov 15 '17 at 13:50
  • Yes, I added `var orderedUsersByLocation = users.Values .OrderBy(ui => Array.IndexOf(locations, ui.Location)).ToList();` that was fine – peterHasemann Nov 15 '17 at 14:02
  • `Array.Reverse(locations); var orderedUsersByLocation = users.Values .Select(ui => new { UserInfo = ui, Index = Array.IndexOf(locations, ui.Location)}) .OrderByDescending(x => x.Index) .Select(x => x.UserInfo); ` By reversing the array the index is in the correct order even when -1 items are returned as items at the top have the highest index value. (you can just write your locations array in the reverse order) – J Whitfield Aug 15 '18 at 09:54
  • Tim, I'm trying to use this answer of yours on a straightfoward Dictionary . Could you enlighten me how you'd do the exact same process as above, but with it being (where 'UserInfo' is a class)? I can create a separate question otherwise. – JackNapier Mar 24 '22 at 21:54
1

Dictionary isn't an ICollection, you'll cannot use OrderBy(), but if you convert to list, you can instanciate an IComparer function :

private class SortCustom : IComparer
{
   private static string[] locations = {
        "europe",
        "america",
        "asia",
        "africa"
    };
   int IComparer.Compare(UserInfo a, UserInfo b)
   {
      return 
           Array.FindIndex(locations, row => row == a.Value.Location).CompareTo(
           Array.FindIndex(locations, row => row == b.Value.Location));
   }
}

Called by :

sortedUsers.OrderBy(x => x, new SortCustom());
GGO
  • 2,678
  • 4
  • 20
  • 42
0

You don't sort a dictionary, you get its values as a list and sort the values based on your criteria, which incidentally would be easiest to do if the criteria were a dictionary:

Dictionary<string,int> locations = new Dictionary<string,int>();
locations["europe"] = 0;
locations["america"] = 1;
locations["asia"] = 2;
locations["africa"] = 3;

//formatted to fit in SO without scrolling
var sortedUsers = 
  users.Values.OrderBy(
    u => locations.ContainsKey(u.Location) ? locations[u.Location] : 999999
  );

foreach (UserInfo info in sortedUsers)
{
    Console.WriteLine("L: " + info.Value.Location + " N: " + info.Value.Name);
}
Caius Jard
  • 72,509
  • 5
  • 49
  • 80
0

You can try to order by your "custom location order" like this:

string[] locations = 
{
    "europe",
    "america",
    "asia",
    "africa"
};

Dictionary<string, UserInfo> sortedUsers = new Dictionary<string, UserInfo>();

foreach (var item in locations)
{
    sortedUsers = sortedUsers.Concat(users.Where(u => u.Value.Location == item)
                             .ToDictionary(d => d.Key, d => d.Value))
                             .ToDictionary(d => d.Key, d => d.Value);
}
SᴇM
  • 7,024
  • 3
  • 24
  • 41
0

You could at first build a sorted list by using IComparer and then create a dictionary from it:

    void Main()
    {
        var d = GetDictionary();
    }

    Dictionary<string,UserInfo> GetDictionary()
    {
        var l = new List<UserInfo>() {
            new UserInfo("One", "There"),
            new UserInfo("Two", "SomewhereB"),
            new UserInfo("Three", "SomewhereA"),
            new UserInfo("Four", "Here")
        };
        var comp = new UserInfoComparer();
        l.Sort(comp);
        return  l.ToDictionary( e => e.Name);
    }

    public class UserInfo
    {
        public string Name { get; set; }
        public string Location { get; set; }
        public UserInfo(string name, string location)
        {
            Name = name;
            Location = location;
        }
    }
    public class UserInfoComparer: IComparer<UserInfo>{
        public int Compare(UserInfo x, UserInfo y)
        {
            var r = x.Location.CompareTo(y.Location);
            if (r == 0)
            {
                r = x.Name.CompareTo(y.Name);
            }
            return r;
        }
    }
Felix Quehl
  • 744
  • 1
  • 9
  • 24