1

I have a list read from database, something like:

var managers = _repo.Where(xxxx).ToList();

data of managers displays in json is

[
    {
        "id":"b2071b2d-3d1a-4403-b044-0a59514c4431",
        "name":"Lucy",
        "mobile":"xxx"
    },
    {
        "id":"639c108f-ec00-4fcd-814a-c3859930b1c1",
        "name":"Franck",
        "mobile":"xxx2"
    },
    {
        "id":"943b2ad4-8ef0-4cf7-824e-de3a7837a1cd",
        "name":"Jerry",
        "mobile":"xxx3"
    }
]

Now I need to reorder this list by a given name sequence, for example:

    var orderByName = _svc.GetManagerSortOrder(); // ["Jerry","Lucy","Franck"]

Right now I'm using foreach to do this, it's too much to code. Is there any better way?

Here is my current solution:

var orderByName = new List<string>() { "Franck", "Jerry" };
var sortedManagers = new List<Manager>();
foreach(string orderName in orderByName) // loop, and sort manager list based on a given name list
{
    var manager = managers.Where(a => a.name == orderName).FirstOrDefault();
    if (manager == null)
        continue;
    sortedManagers.Add(manager);
}

if(sortedManagers.Count != managers.Count) // if counts not equal, there is someone not on the name list, so these men need to be appended to the sortedManagers list
{
    var sortedManagersId = sortedManagers.Select(s => s.id);
    var managersNotInclude = managers.Where(a => !sortedManagersId.Contains(a.id)).ToList();
    sortedManagers = sortedManagers.Concat(managersNotInclude).ToList();
}
wtf512
  • 4,487
  • 9
  • 33
  • 55

3 Answers3

2

Unfortunately the following answer would help you, if it wasn't for handling names in your source list, that don't exist in your target list since IndexOf() returns -1 for undiscovered elements: Sort a list from another list IDs

What you need to do is write a customer Comparer that implements ICompare. This could look something like this:

class DependentComparer<T> : IComparer<T>
{
    // Backing field to contain the order dependent list
    List<T> lookUpTable;

    public DependentComparer(List<T> lookUpTable)
    {
        this.lookUpTable = lookUpTable;
    }

    public int Compare(T x, T y)
    {
        // Determine the index of the compared elements in the dependent list
        int xIndex = lookUpTable.IndexOf(x);
        int yIndex = lookUpTable.IndexOf(y);

        // If neither were in the dependent list, they are equal
        if ((xIndex == -1) && (yIndex == -1)) return 0;

        // If only the y was found, then y is greater than the x element
        if (xIndex == -1) return 1;
        // If only the x was found, then y is less than the x element
        if (yIndex == -1) return -1;

        // If both were found, then return the delta of their indicies
        return xIndex - yIndex;
    }
}

This can be used as:

var orderByName = new List<string>() { "Frank", "Jerry" };
var managersNames = new List<string>() { "Jerry", "Frank", "Lucy" };

managersNames.Sort(new DependentComparer<string>(orderByName));

Output: Frank, Jerry, Lucy

NOTES: This method will take no sorting action on the elements not pressent in the lookUpTable. They will be appended to the end of result in the order in which they were in the source. Also, the example is given as if the lists were of string, not manager. The conversion is easy enough, but let me know if you need an edit.

George Kerwood
  • 1,248
  • 8
  • 19
  • Thanks George. I never thought `ICompare` can be used for this. Your and Harald's advice helps me a lot! – wtf512 Jun 05 '20 at 03:10
1

So you have a sequence of Managers where every Manager has a Name; and you have a sequence of Names, something like ["Jerry","Lucy","Franck"].

You want to order your Managers, such that you first get all Managers named "Jerry", then all Managers names "Lucy", etc. You want to end with managers that were not in your sequence of names.

Consider creating an object that implements IComparer<Manager>: get two Managers, and decides who should go first. For instance: one Manger is named "Lucy", the other one is named "Jerry", so "Jerry" should bo first.

Usage would be:

IEnumerable<Manager> unorderedManagers = ...
IComparer<Manager> managerComparer = ...

IEnumerable<Manager> orderedManagers = unorderedManagers.OrderBy(managerComparer);

So let's create a ManagerComparer class.

class ManagerComparer : IComparer<Manager>
{
    public ManagerComparer(IEnumerable<string> names)
    {
    }

    public int Compare(Manager x, Manager y) {...}
    // TODO implement
}

If you have two managers as input: x and y, you want the manger with "Jerry" first. If there is no "Jerry" you want "Lucy" first, etc.

To make this efficient, we put all Names in a dictionary: Key is the name, Value is the index. So "Jerry" has index 0, "Lucy" index 1, etc

You get Manager X and Y: get their Names, get the index from the Dictionary. The one with the lowest index comes first.

Sounds like a good idea? Let's do it:

private readonly IDictionary<string, int> managerNames;

public ManagerComparer(IEnumerable<string> names)
{
    // TODO: what to do if no names?

    this.managerNames = names.Select( (name, i) => new
    {
        Name = name,
        Index = i,
    })
    .ToDictionary(item => item.Name, item => item.Index);
}

The Compare method.

We know what to do when you get two Managers. Comparer the indexes of the names in the Dictionary. Return either -1 / 0 / +1, depending on the value. This can be done with if/then/else, it is easier to use the existing IComparer<int>:

private static readonly IComparer<int> indexComparer = Comparer<int>.Default;


public int Compare(Manager x, Manager y)
{
    // TODO: decide what to return if x or y null: first or last in your result
    if (x == null)
    {
        if (y == null)
           return 0;
        else
            return ... // -1 or +1;
    }
    else if (y == null)
        return ... // -1 or +1;

    // if here, both x and y not null
    string nameX = x.Name;
    string nameY = y.Name;

    int indexX = this.managerNames[nameX];
    int indexY = this.managerNames[nameY];

    // the one with the lowest index comes first:
    return indexComparer.Compare(indexX, indexY);
}

Again usage:

IEnumerable<Manager> unorderedManagers = ...
IEnumerable<string> orderByNames = new sting[] {"Jerry", "Lucy", ...}
IComparer<Manager> managerComparer = new ManagerComparer(orderByNames);

IEnumerable<Manager> orderedManagers = unorderedManagers.OrderBy(managerComparer);
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Respectfully, beyond removing the generics (which arguably make it less reusable), I do not see what this answer adds beyond that which I have provided. If you felt my answer lacking, fairer to comment and request edits/expansions rather than post an extended duplicate. – George Kerwood Jun 03 '20 at 08:44
  • When I started editing, your answer wasn't there yet. Do you want me to remove my answer? – Harald Coppoolse Jun 03 '20 at 14:10
  • No, thank you, benefit of the doubt. It just seemed odd after a couple of hours. It's a very full answer which will no doubt help people. – George Kerwood Jun 03 '20 at 14:21
  • I started answering during a break from work. Hours later, during my next break I continued – Harald Coppoolse Jun 03 '20 at 14:27
  • It is a very detailed step by step guide for me. And helps me a lot. I never thought about extend IComparer before. Thanks! – wtf512 Jun 05 '20 at 03:06
0

A very concise and fast method is offered by Array.Sort that sorts an array according to the order of a given array containing the prescribed order.

var managers = JsonConvert.DeserializeObject<IEnumerable<Manager>>(json).ToArray();
var order = new List<string> { "Jerry", "Lucy", "Franck" };
var indexes = managers
    .Select(m => order.IndexOf(m.Name))
    .Select(i => i == -1 ? int.MaxValue : i)
    .ToArray();

Array.Sort(indexes, managers);

The array indexes looks something like [1,2147483647,2,2147483647,0] when managers contains 5 names with the given names at positions 1, 3, and 5 (Jerry at 5, getting index 0, etc.).

Array.Sort "lines up" both arrays and orders the lineup by the the values in indexes. The result is managers, sorted according to the prescribed order. Note that the physical order of elements in managers is persistently changed.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291