2

There is a list of Package item is sorted by GUID, but I need to order them as follows

KK %, AB, AB art, DD %, FV, ER, PP and WW

I have implemented as follows, but I wonder is there a better way of doing it?

List<PackageType> list = new List<PackageType> (8);           
foreach (var a in mail.Package)
{
 if (a.Name == "KK %")
    list[0] = a;
 else if (a.Name == "AB art")
    list[1] = a;
 else if (a.Name == "AB")
    list[2] = a;
 else if (a.Name == "DD %")
    list[3] = a;
 else if (a.Name == "FV")
    list[4] = a;
 else if (a.Name == "ER")
    list[5] = a;
 else if (a.Name == "PP")
    list[6] = a;
 else if (a.Name == "WW")
   list[7] = a;
}
casillas
  • 16,351
  • 19
  • 115
  • 215

5 Answers5

9

You can get this down to two lines (one for array definition, one for ordering):

var PackageOrder = new[] { "KK %", "AB", "AB art", "DD %", "FV", "ER", "PP", "WW"};

//...

var list =  mail.Package.OrderBy(p => Array.IndexOf(PackageOrder, p.Name)).ToList();

But we can do even better.

The code so far either requires several O(n) lookups into the reference array, or switching to a Dictionary<string,int>, which is O(1) for each lookup for a value of 1 that might be disproportionate to the task. Each package item may need several of these lookups over the course of a sort operation, which means this might be less efficient than you want.

We can get around that like this:

private static string[] Names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };

//...

var list =  mail.Package.
           Select(p => new {Package = p, Index = Array.IndexOf(Names, p.Name)}).
           OrderBy(p => p.Index).
           Select(p => p.Package).ToList(); 

This guarantees only one lookup per package over the course of the sort. The idea is to first create a projection of the original data that also includes an index, then sort by the index, and finally project back to just the original data. Now the only question is whether to use an array or dictionary, which mainly depends on the size of the reference array (for this size data stick with the array, for more than about 15 items, switch to the dictionary; but it varies depending on the GetHashCode() performance of your type).

Of course, there's also YAGNI to consider. For large sets this will typically be much better, but for small data it might not be worth it, or if the data happens to be sorted in a certain lucky ways it can make things slower. It can also make things slower if your are more constrained by memory pressure than cpu time (common on web servers). But in the general sense, it's a step in the right direction.

Finally, I question the need for an actual List<T> here. Just change the declaration to var and remove the .ToList() at the end. Wait to call ToList() or ToArray() until you absolutely need it, and work with the simple IEnumerable<T> until then. This can often greatly improve performance.

In this case (and for reference, I added this paragraph later on), it seems like you only have eight items total, meaning the extra code isn't really saving you anything. With that in mind, I'd just stick with the two-line solution at the top of this answer (when performance doesn't matter, go for less or simpler code).

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
6
// List<PackageType> list = ...;
var names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };
var sortedList = list.OrderBy(packageType => Array.IndexOf(names, packageType.Name));

Here's a longer version of the above that explains in more detail what's going on:

// this array contains all recognized keys in the desired order;
private static string[] Names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };

// this helper method will return the index of a `PackageType`'s `Name`
// in the above array, and thus a key by which you can sort `PackageType`s.
static int GetSortingKey(PackageType packageType)
{
    var sortingKey = Array.IndexOf(Names, packageType.Name);
    if (sortingKey == -1) throw new KeyNotFoundException();
    return sortingKey;
}

// this is how you could then sort your `PackageType` objects:
// List<PackageType> list = ...;
IEnumerable<PackageType> sortedList = list.OrderBy(GetSortingKey);
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
2

Updated version of @stakx answer. Well I think this should be better solution, if these values are fixed, also can be used elsewhere. Each value in enum has its int value, by which they can be ordered.

public enum Names 
{
   KK, //0
   AB, //1
   BC, //2
   DD, //3
   FV, //4
   ER, //5
   PP, //6
   WW //7
}

var packageList = list.OrderBy(p => p.Name);

UPDATE

Use example for enum in class

public class Package 
{
   public Names Name { get; set; }
}

If there is need to get string value of this enum, then just use this

Package package - Package class variable

package.Name.ToString();

If you need whole list of enum names (enum key names), you can use Enum class method:

Enum.GetNames(Type enumType) which returns string array with all defined enum key names.

Enum.GetNames(typeof(Names))
Imants Volkovs
  • 838
  • 11
  • 20
1

An alternative.. (I haven't compiled it)

var indexPositions = new Dictionary<string, int> {
    { "KK", 0 },
    { "AB", 1 },
    { "BC", 2 },
    { "DD", 3 },
    { "FV", 4 },
    { "ER", 5 },
    { "PP", 6 },
    { "WW", 7 }
}
foreach (var package in mail.Package)
{   // access position
    int index;
    if (!indexPositions.TryGetValue(a.Name, out index)) {
        throw new KeyNotFoundException()
    }
    list[index] = package;
}
Gonzalo.-
  • 12,512
  • 5
  • 50
  • 82
  • Your key and value pairs for the dictionary are mixed up. – juharr Mar 21 '17 at 21:23
  • 1
    You had the right sort of dictionary with a bug in how it was being built, now you have the wrong sort of dictionary being built okay. – Jon Hanna Mar 21 '17 at 21:36
  • @JonHanna you're right, I updated it. This is what happens when you try to answer in SO while working - having two things in mind leads to this kind of answers lol – Gonzalo.- Mar 21 '17 at 21:38
1

In addition to the usual OrderBy Array.IndexOf method, the list can also sorted in-place:

string[] order = { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };

list.Sort((a, b) => Array.IndexOf(order, a.Name).CompareTo(Array.IndexOf(order, b.Name)));

A bit more advanced O(n)ish alternative:

var lookup = list.ToLookup(x => x.Name);

list = order.SelectMany(x => lookup[x]).ToList();
Slai
  • 22,144
  • 5
  • 45
  • 53