Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert LINQ orderby to inplace list sort

Currently, i'm sorting a list using LINQ to objects, then doing a ToList() on the results:

var SortedPossibleMoveLocations = (from PML in PossibleMoveLocations
                                   orderby Randomiser.Next()
                                   orderby IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0
                                   orderby PossibleMoveLocationOrdering(PML)
                                   select PML).ToList();

I want to convert this to do an inplace sort, i guess using List<T>.Sort() method. If i was only ordering by one thing i'd know how to do this, however, as i'm ordering by PossibleMoveLocationOrdering (which returns an int) then by IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0 which evaluates to an int, then by Randomiser.Next() (which returns a random int) i don't know how to do this.

Question: How do i write the comparison function (or is there a better method) to do an implace sort of the LINQ query above.

like image 744
George Duckett Avatar asked Jun 13 '11 09:06

George Duckett


2 Answers

To start with, specifying three orderby clauses is a bad idea - instead, specify multiple orderings just by using comma separation.

I'm also not keen on the idea of using Randomiser.Next() for ordering, but that's an aside.

Your LINQ query should look like this (still with Randomiser in for the moment):

var query = (from PML in PossibleMoveLocations
             orderby PossibleMoveLocationOrdering(PML),
                     IsSameType(PML) ? (_Owner[PML] as TileFlowing).UnitsWithin : 0,
                     Randomiser.Next()
             select PML).ToList();

Personally I'd just use dot notation for this:

var query = PossibleMoveLocations
                .OrderBy(pml => PossibleMoveLocationOrdering(PML))
                .ThenBy(pml => IsSameType(pml) ? 
                                    (_Owner[pml] as TileFlowing).UnitsWithin : 0)
                .ThenBy(pml => Randomiser.Next())
                .ToList();

To sort in place, you basically need a Comparison<T> or IComparer<T> which can test multiple things, and also an implementation which creates a comparer using properties. You can do that manually (as per Marc's code), but as it happens, I have some helper classes and extension methods in MiscUtil:

var comparer = ProjectionComparer<PossibleMove>
                   .Create(pml => PossibleMoveLocationOrdering(PML));
                   .ThenBy(pml => IsSameType(pml) ? ...)
                   .ThenBy(...);

list.Sort(comparer);

Note that using a Randomizer here is definitely a bad idea, as it will be called on each comparison (for objects with equal first parts)... this can lead to an inconsistent comparison such that x < y < z < x.

like image 197
Jon Skeet Avatar answered Sep 21 '22 06:09

Jon Skeet


Most commonly:

list.Sort((x,y) => {
    int result = /* first comparison, for example
                    string.Compare(x.Name, y.Name) */
    if (result == 0) result = /* second comparison,
                                 for example x.Id.CompareTo(y.Id) */
    ...
    if (result == 0) result = /* final comparison */
    return result;
});

or similar (perhaps in a comparer class, if it is non-trivial).

like image 37
Marc Gravell Avatar answered Sep 20 '22 06:09

Marc Gravell