Alessandro
Alessandro

Reputation: 3760

Sort list based on multiple conditions

I have a list of integer lists, like that:

A -> 10 10 1 1 1
B -> 10  9 9 7 6
...

I would like to sort them based on how many 10s they have, then on how many 9s, 8s, 7s, and so on untile the 1s

So in the example above A should be better than B because even if it has less total points, it has two 10s instead of only 1.

Code should be generic because I don't know how many numbers will be available for each case (sometimes 10, sometimes 5, or even only 3).

I developed something like that:

lists.OrderByDescending(a => a.Where(b => b == 10).Count()).
ThenByDescending(a => a.Where(b => b == 9).Count()).

and so on, but this is not generic...

I hope the question is clear... thank you very much!

Upvotes: 0

Views: 1376

Answers (3)

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236228

You can create query which orders lists by count of 10s, then compose query by adding additional orderings for numbers from 9 to 1:

var query = lists.OrderByDescending(l => l.Count(x => x == 10));

for (int i = 9; i >= 1; i--)
    query = query.ThenByDescending(l => l.Count(x => x == i));

For these sample lists:

var lists = new[] { 
    new[] { 10, 9, 9, 8, 7 }, 
    new[] { 10, 9, 9, 7, 6 },
    new[] { 10, 10, 1, 1, 1 }
};

Result will be:

[10, 10, 1, 1, 1]
[10, 9, 9, 8, 7]
[10, 9, 9, 7, 6]

It's simple, but not very efficient. If you need better performance, then consider creating custom comparer. Here is sample with comparer which uses zipped ordered sequences to check if all items in sequences are same, or get first item which is different:

public class CustomComparer : Comparer<IList<int>>
{
    public override int Compare(IList<int> x, IList<int> y)
    {            
        var comparisons = x.Zip(y, (a,b) => a.CompareTo(b));

        foreach(var comparison in comparisons)
        {
            if (comparison != 0)
                return comparison;
        }

        return x.Count.CompareTo(y.Count);
    }
}

NOTE: If items in lists are not ordered, then you should sort them before zipping:

var comparisons = 
    x.OrderByDescending(i => i)
     .Zip(y.OrderByDescending(i => i), (a,b) => a.CompareTo(b));

It works very simple. Consider two lists:

[10, 9, 9, 8, 7, 5]
[10, 9, 9, 7, 6]

It will create pairs of items in corresponding positions:

{10,10}, {9,9}, {9,9}, {8,7}, {7,6}

Then items in each pair will be compared one by one, until first mismatch will be found:

0, 0, 0, 1 (four comparisons only)

That means first list has more 8s than second one. Usage:

var query = lists.OrderByDescending(i => i, new CustomComparer());

Result is same.

Upvotes: 2

Rawling
Rawling

Reputation: 50114

The following comparer

public class Comparer : IComparer<IEnumerable<int>>
{
    public int Compare(IEnumerable<int> a, IEnumerable<int> b)
    {
        var aOrdered = a.OrderByDescending(i => i).Concat(new[] { int.MinValue });
        var bOrdered = b.OrderByDescending(i => i).Concat(new[] { int.MinValue });
        return a.Zip(b, (i, j) => i.CompareTo(j)).FirstOrDefault(c => c != 0);
    }
}

lets you order you lists of lists like so

var result = lists.OrderByDescending(i => i, new Comparer());

without iterating through each list ten times counting individual elements.

Upvotes: 1

Axarydax
Axarydax

Reputation: 16603

This compares the lists and returns conventional comparison result - 1, 0, or -1 is returned depending on whether one value is greater than, equal to, or less than the other.

    static int CompareLists(List<int> a, List<int> b)
    {
        var grpA = a.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
        var grpB = b.GroupBy(p => p).ToDictionary(k=>k.Key,v=>v.Count());
        for (int i = 10; i >= 0; i--)
        {
            int countA = grpA.ContainsKey(i) ? grpA[i] : 0;
            int countB = grpB.ContainsKey(i) ? grpB[i] : 0;
            int comparison = countA.CompareTo(countB);
            if (comparison != 0)
                return comparison;
        }
        return 0;
    }

First we convert the lists into dictionary of number->amount of occurences. Then we iterate through numbers from 10 to 0 and compare the number of occurences. If the result is 0, then we go to another number.

If you have List<List<int>> to sort, just use list.Sort(CompareLists) as in:

        List<int> d = new List<int> { 10, 6, 6 };
        List<int> b = new List<int> { 10, 9, 9 };
        List<int> a = new List<int> { 10, 10, 1, 1, 1 };
        List<int> c = new List<int> { 10, 7, 7 };
        List<int> e = new List<int> { 9, 3, 7 };
        List<int> f = new List<int> { 9, 9, 7 };
        List<List<int>> list = new List<List<int>>() { a, b, c, d, e, f };
        list.Sort(CompareLists);

Upvotes: 1

Related Questions