6

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.

Tim Liberty
  • 2,099
  • 4
  • 23
  • 39
  • Check this http://stackoverflow.com/questions/3062513/how-can-i-sort-generic-list-desc-and-asc Or you are looking for [Intersection](http://www.dotnetperls.com/intersect) – Kaushik Mar 29 '16 at 05:34
  • If you want to do in the same list, then start swapping the elements in the original list based on index of the order array. There will not be a default mechanism – Mrinal Kamboj Mar 29 '16 at 05:35
  • You can joint your array with your list and then `Select` with no ordering. – Vahid Mar 29 '16 at 05:37
  • @ Unknown User that would not work, since we need a specific comparison logic to create an IComparer, here there seems no such system – Mrinal Kamboj Mar 29 '16 at 05:37
  • @MrinalKamboj I think intersection can solve the problem. – Kaushik Mar 29 '16 at 05:40

4 Answers4

9

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.

Community
  • 1
  • 1
Serge
  • 3,986
  • 2
  • 17
  • 37
  • You may want to handle the mismatch between list and array values, by using a default to put the number in the end. Assumption that they always same may fail in practical case, in fact it would fail for List having more number of elements – Mrinal Kamboj Mar 29 '16 at 06:03
  • If it's possible that some elements will be missing from `newOrder`, then @MrinalKamboj's suggestion is correct. In this case, you should use something like `orderedTestsSafe`. – Serge Mar 29 '16 at 06:11
  • Use the index map is great, but any item not matched to the new order list will be ordered as random due to `Int32.MaxValue`, but it may not required by OP. Good answer – Eric Mar 29 '16 at 06:16
2
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.

Pedro G. Dias
  • 3,162
  • 1
  • 18
  • 30
2

Using left join can be one of the way to use the customized ordering. For any item not match to the customized ordering, use original order of the list starting at the end

var results = from a in tests.Select((r, i) => new {item = r, Index = i})
              // left join
              from b in newOrder.Select((r, i) => new { item = r, Index = i })
                                .Where(b => a.item.No == b.item).DefaultIfEmpty() 
              // Not in order list then use original ordering
              orderby (b == null ? tests.Count() + a.Index : b.Index) 
              select a.item;

.Net Fiddle

Eric
  • 5,675
  • 16
  • 24
1

Try joining your array and list like this

newOrder.Join(tests, no => no, tst => tst.No, (no, tst) => tst)
Vahid
  • 1,829
  • 1
  • 20
  • 35
  • Very good solution as it works by joining but what when original list has numbers which are not part of array, would Left Outer Join a better choice – Mrinal Kamboj Mar 29 '16 at 06:05