Jay Nanavaty
Jay Nanavaty

Reputation: 1129

Sort list based on calculated index of properties

I have following class.

 public class Header
    {
        public long CombinedIndex { get; private set; }
        public int Key { get; set; } //Unique
        public bool IsRequired { get; set; }// True or false
        public DateTime? AvailableDate { get; set; } // null or date value
        public int Index { get; set; } // a number and can be same among other Header 

        public void CalculateCombinedIndex()
        {
            CombinedIndex = Key + (IsRequired ? 0 : 1) + Index + (AvailableDate ?? DateTime.MaxValue).Ticks;
        }
    }

I am expected around 50,000 instances of it in an array. I need to sort them in following order:

Does it make sense to say sum all these property value and derive a unique number and then only sort that number. Later, the list of Header can be converted to dictionary where Key is CombinedIndex and my already sorted array can lookup that dictionary in order I iterated the array. How efficient and promising this all looks?

Upvotes: 0

Views: 138

Answers (3)

Douglas
Douglas

Reputation: 54877

If you need to perform comparison operations based on a projected key from a given type, I find it helps to have a KeyComparer class that can create an IComparer<TSource> instance from a Func<TSource, TKey> delegate. This works especially well with the new tuple syntax introduced in C# 7. Here's an example of the resulting syntax:

// Create an IComparer<Header> instance based on your combined key.
var comparer = KeyComparer.Create((Header h) => (h.IsRequired, h.Index, h.AvailableDate, h.Key));

List<Header> headers = ...

// Sort the list using the combined key.
headers.Sort(comparer);

// Convert to a dictionary keyed by the combined key.
var dict = headers.ToDictionary(comparer.KeySelector);

And here's a sample implementation of the class. (For a more detailed explanation, see my blog post.)

public static class KeyComparer
{
    public static KeyComparer<TSource, TKey> Create<TSource, TKey>(
        Func<TSource, TKey> keySelector,
        IComparer<TKey> innerComparer = null)
    {
        return new KeyComparer<TSource, TKey>(keySelector, innerComparer);
    }
}

public class KeyComparer<TSource, TKey> : Comparer<TSource>
{
    protected internal KeyComparer(
        Func<TSource, TKey> keySelector,
        IComparer<TKey> innerComparer = null)
    {
        KeySelector = keySelector ?? throw new ArgumentNullException(nameof(keySelector));
        InnerComparer = innerComparer ?? Comparer<TKey>.Default;
    }

    public Func<TSource, TKey> KeySelector { get; }
    public IComparer<TKey> InnerComparer { get; }

    public override int Compare(TSource x, TSource y)
    {
        if (object.ReferenceEquals(x, y))
            return 0;
        if (x == null)
            return -1;
        if (y == null)
            return 1;

        TKey xKey = KeySelector(x);
        TKey yKey = KeySelector(y);
        return InnerComparer.Compare(xKey, yKey);
    }
}

Upvotes: 1

paparazzo
paparazzo

Reputation: 45096

Just implement IComparable and you can use List.Sort().

public class Header : IComparable
{
    public long CombinedIndex { get; private set; }
    public int Key { get; set; } //Unique
    public bool IsRequired { get; set; }// True or false
    public DateTime? AvailableDate { get; set; } // null or date value
    public int Index { get; set; } // a number and can be same among other Header 

    public void CalculateCombinedIndex()
    {
        CombinedIndex = Key + (IsRequired ? 0 : 1) + Index + (AvailableDate ?? DateTime.MaxValue).Ticks;
    }

    public int CompareTo(object obj)
    {
        if (obj == null) return 1;

        Header otherHeader = obj as Header;
        if (otherHeader != null)
        {
            if (this.IsRequired && !otherHeader.IsRequired)
                return 1;
            if (!this.IsRequired && otherHeader.IsRequired)
                return -1;
            if (this.Index > otherHeader.Index)
                return 1;
            if (this.Index < otherHeader.Index)
                return -1;
            //// ....
            return 0;
        }
        else
            throw new ArgumentException("Object is not a Temperature");
    }
}

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460098

I would do it with LINQ:

var ordered = list
    .OrderByDescending(x => x.IsRequired)
    .ThenBy(x => x.Index)
    .ThenBy(x=> x.AvailableDate)
    .ThenBy(x=> x.Key);

Only in case of ties the next condition will be used. So no need to create a combined index which makes it very difficult to change the order logic and which seems to be broken anyway.

Upvotes: 2

Related Questions