Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ordering list by a specific order

Tags:

c#

.net

I have a List and I have the new order in which the List should have the items in int[] I want that the item in List should be re-ordered as per the items in int[]. Here is my code to do that:

   class Program
    {
        static void Main(string[] args)
        {
            List<Test> tests = new List<Test>() { 
                new Test(){ No = 201 },
                new Test(){ No = 101 },
                new Test(){ No = 300 },
                new Test(){ No = 401 },
                new Test(){ No = 500 },
                new Test(){ No = 601 }
            };


            int[] newOrder = new int[6] { 201, 401, 300, 101, 601, 500 };

            //after the opration the List should contain items in order 201, 401, 300, 101, 601, 500

            List<Test> newTests = new List<Test>();

            foreach(var order in newOrder)
            {
                var item = tests.SingleOrDefault(t => t.No == order);

                if (item != null)
                    newTests.Add(item);
            }


        }

    }

This works fine. But it creates a separate List and performs operation on it. Is there better way where I can use may be built in .Net operation for this or may be perform the operation on the same List without creating these Temp List etc?

Thank you.

like image 326
Tim Liberty Avatar asked Mar 29 '16 05:03

Tim Liberty


2 Answers

You need to think about performance when running a sort like this.

If you expect only a handful of elements, then Pedro's solution is OK.

If you expect to have many elements (say, 100's or 1000's), then it's not a good idea to search through entire collection of tests for each element in newOrder. In this case, it would be helpful to use a Dictionary for all index/sort-order lookups. Try something like this:

List<Test> tests = new List<Test>() { 
    new Test(){ No = 101 },
    new Test(){ No = 201 },
    new Test(){ No = 300 },
    new Test(){ No = 401 },
    new Test(){ No = 500 },
    new Test(){ No = 601 }
};


int[] newOrder = new int[6] { 201, 401, 300, 101, 601, 500 };

// Create a Dictionary/hashtable so we don't have to search in newOrder repeatedly
// It will look like this: { {201,0}, {401,1}, {300,2}, {101,3}, {601,4}, {500,5} }
Dictionary<int, int> newOrderIndexedMap = Enumerable.Range(0, newOrder.Length - 1).ToDictionary(r => newOrder[r], r => r);

// Order using 1 CPU
var orderedTests = tests.OrderBy(test => newOrderIndexedMap[test.No]);
// Order using multi-threading
var orderedInParallelTests = tests.AsParallel().OrderBy(test => newOrderIndexedMap[test.No]);

// Order using 1 CPU, when it's possible that a match will not be found in newOrder
var orderedTestsSafe = tests.OrderBy(test => 
    {
        int index;
        bool foundIndex = newOrderIndexedMap.TryGetValue(test.No, out index);
        return foundIndex ? index : Int32.MaxValue;
    });

Note that both this answer and Pedro's assume that newOrder contains all values contained in the tests elements and vice versa.

like image 71
Serge Avatar answered Sep 21 '22 23:09

Serge


var newTesties= newOrder.Select(o => tests.First(t => t.No == o));

Basically, I'm selecting every number 'o' in newOrder, and using that to pick up the corresponding test. You WILL end up with a new list though.

like image 31
Pedro G. Dias Avatar answered Sep 21 '22 23:09

Pedro G. Dias