KHans
KHans

Reputation: 89

Order object by enum in custom order

I'm trying to rearrange a list of animals in order from medium, large, small. I've been trying to do this with IComparable.CompareTo but I can't figure out how to order it in this specific way. I can only find ways to order by Ascending or Descending values.

My enum:

public enum AnimalSize
    {
        Small = 1, Medium = 3, Large = 5,
    }

My Animal class:

public class Animal : IComparable<Animal>
{
    public bool IsCarnivore;
    public AnimalSize Size;

    public Animal(bool isCarnivore, AnimalSize size)
    {
        this.IsCarnivore = isCarnivore;
        this.Size = size;
    }

    public int CompareTo(Animal other)
    {
        return this.Size.CompareTo(other.Size);
    }

I call the CompareTo from another class where I have a private list of animals.

How can I arrange the animals in order of Medium, Large, Small?

Upvotes: 2

Views: 2330

Answers (4)

Slai
Slai

Reputation: 22896

You can check if any of them are Medium before comparing (not tested) :

public int CompareTo(Animal other)
{
    if (this.Size == other.Size) return 0;
    if (this.Size == AnimalSize.Medium) return -1;
    if (other.Size == AnimalSize.Medium) return 1;

    return this.Size.CompareTo(other.Size);
}

A bit more efficient without branch misprediction (3 becomes 1, 5 ⇒ 3, and 1 ⇒ 7) :

public int CompareTo(Animal other) => ((int)this.Size + 2 ^ 4) - ((int)other.Size + 2 ^ 4);

or lookup array (3 ⇒ 1, 5 ⇒ 2, and 1 ⇒ 3) :

byte[] order = { 0, 3, 0, 1, 0, 2 };

public int CompareTo(Animal other) => order[(byte)this.Size] - order[(byte)other.Size];

Upvotes: 0

John Wu
John Wu

Reputation: 52280

I would not implement IComparable<T> for a non-scalar object such as an animal. What if you want to sort by height instead of size? Or by name? IComparable implies that the object can be converted to a one-dimensional quantity.

Instead, define the sort order you need in an array, and use a simple LINQ query to sort.

public static void Main()
{
    var sampleData = new List<Animal>
    {
        new Animal(false, AnimalSize.Small),
        new Animal(false, AnimalSize.Large),
        new Animal(false, AnimalSize.Medium)
    };

    AnimalSize[] customSortOrder = new[]
    {
        AnimalSize.Small,
        AnimalSize.Medium,
        AnimalSize.Large
    };

    var results = sampleData.OrderBy( a => Array.IndexOf(customSortOrder, a.Size ));

    foreach (var a in results)
    {
        Console.WriteLine(a.Size);
    }

Output:

Small
Medium
Large

Code on DotNetFiddle

Upvotes: 12

Gwen Royakkers
Gwen Royakkers

Reputation: 441

If you want to use IComparable, you'll need to convert to int first.

public int CompareTo(Animal other)
        {
            return ((int)this.Size).CompareTo((int)other.Size);
        }

With this change, calling the Sort() function of a list containing animal objects will work as expected.

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460360

You could create a custom Comparer<Animal>:

public class AnimalSizeComparer : Comparer<Animal>
{
    private readonly IList<AnimalSize> _order;

    public AnimalSizeComparer():this(Enum.GetValues(typeof(AnimalSize)).Cast<AnimalSize>().ToArray())
    {
    }
    public AnimalSizeComparer(IList<AnimalSize> order)
    {
        _order = order;
    }

    public override int Compare(Animal x, Animal y)
    {
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;

        return _order.IndexOf(x.Size).CompareTo(_order.IndexOf(y.Size));
    }
}

You initialize it with your sample order in this way:

var comparer = new AnimalSizeComparer(new[] { AnimalSize.Medium, AnimalSize.Large, AnimalSize.Small });
yourAminalList.Sort(comparer);

Upvotes: 1

Related Questions