nicktheone
nicktheone

Reputation: 87

Sorting a List<T> based on T's List<string> property

I have a custom object Deck that has a List<Card> cards between its properties. Each Card has its own List<string> colors denoting the color of the card using one or more capital letters like such [W, U, B, R, G].

What I need to do is sort the cards list based on the colors list in a way that I get first all the cards from one color and so on for each color; with cards having more than one color I'd like them being sorted based on a custom priority list (like if it's W and U put it between W cards) but I realize this is even more complex so it's not really a necessity for me.

What I tried to do is

deck.cards = deck.cards.OrderBy(x => x.colors).ToList();

but I receive an error stating that at least an object needs to implement ICompare.

What can I do to sort my deck list? Is it possible to not only sort it like described but also based on a specific order like B before R before G and so on?

Upvotes: 0

Views: 410

Answers (2)

canton7
canton7

Reputation: 42225

Based on the discussion in the comments, when a card has multiple colours, you want to select a single colour (the one that appears first in a priority list), and sort it on that basis.

// Higher-priority colours come first
var coloursPriority = new List<string>() { "W", "U", "B", "R", "G" };

// Turn the card's colour into an index. If the card has multiple colours,
// pick the smallest of the corresponding indexes.
cards.OrderBy(card => card.Colours.Select(colour => coloursPriority.IndexOf(colour)).Min());

Responding to the discussion in the comments: if you wanted to sort the cards based first on their highest-priority colour, and then by their next-highest-priority colour, etc, then this is a reasonably efficient way of doing it:

public class CardColourComparer : IComparer<List<int>>
{
    public static readonly CardColourComparer Instance = new CardColourComparer();
    private CardColourComparer() { }

    public int Compare(List<int> x, List<int> y)
    {
        // Exercise for the reader: null handling

        // For each list, compare elements. The lowest element wins
        for (int i = 0; i < Math.Min(x.Count, y.Count); i++)
        {
            int result = x[i].CompareTo(y[i]);
            if (result != 0)
            {
                return result;
            }
        }

        // If we're here, then either both lists are identical, or one is shorter, but it
        // has the same elements as the longer one.
        // In this case, the shorter list wins
        return x.Count.CompareTo(y.Count);
    }
}

Then

// Higher-priority colours come first
var coloursPriority = new List<string>() { "W", "U", "B", "R", "G" };

cards.OrderBy(card =>
    card.Colours.Select(colour => coloursPriority.IndexOf(colour)).OrderBy(x => x).ToList(),
    CardColourComparer.Instance);

This takes advantage of the fact that OrderBy applies the keySelector delegate to each item once only. We use this to turn each card into a list containing the priority of each of its colours (higher priorities have lower values), ordered with the higher-priority ones first. We then sort these keys, using a custom comparer which compares two of these lists.

Note that this doesn't care about the order of the colours associated with each card: [W, U] will sort the same as [U, W]. To take the order into account (so [W] comes before [W, U] comes before [U, W], do this:

cards.OrderBy(card =>
    card.Colours.Select(colour => coloursPriority.IndexOf(colour)).ToList(),
    CardColourComparer.Instance);

Upvotes: 4

melkisadek
melkisadek

Reputation: 1053

You can get a list of ordered cards by using the Aggregate function as follows:

var result = deck.Cards
    .OrderBy(x => x.Colours.Aggregate((total, part) => total + part.ToLower()))
    .ToList();

This assumes that the cards with multiple colours have those in an ordered list.

e.g.

    card1.Colours = new List<string>() { "W", "X" };
    card2.Colours = new List<string>() { "W" };
    card3.Colours = new List<string>() { "U" };
    card4.Colours = new List<string>() { "U", "W" };

Will return the cards in the order:

"U", "UW", "W", "WX"

Upvotes: 1

Related Questions