0

I want to write a sorting logic to sort the below strings as output of a custom class:

Output Now: 3m_20,2m_20,1m_20,10d_20,5d_20,0d_20,0d_0,5d_0,10d_0,1m_0,2m_0,3m_0

Required Output: 0d_0,0d_20,5d_0,5d_20,10d_0,10d_20,1m_0,1m_20,2m_0,2m_20,3m_0,3m_20

I am finding it difficult to complex to sort in the above fashion. Please help how can I sort as required ?

I have tried to sort it with the IComparable on the basis of Id but its sorting on the basis of the first character.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Rextester
{
    public class Program
    {
        public static void Main(string[] args)
        {
            DateDefinition d1 = new DateDefinition { Horizon = "0d",Days=20};
            DateDefinition d1_0 = new DateDefinition { Horizon = "0d",Days=0};
            DateDefinition d2 = new DateDefinition { Horizon = "5d",Days=20};
            DateDefinition d2_0 = new DateDefinition { Horizon = "5d",Days=0};
            DateDefinition d3 = new DateDefinition { Horizon = "10d",Days=20};
            DateDefinition d3_0 = new DateDefinition { Horizon = "10d",Days=0};
            DateDefinition d4 = new DateDefinition { Horizon = "1m",Days=20};
            DateDefinition d4_0 = new DateDefinition { Horizon = "1m",Days=0};
            DateDefinition d5 = new DateDefinition { Horizon = "2m",Days=20};
            DateDefinition d5_0 = new DateDefinition { Horizon = "2m",Days=0};
            DateDefinition d6 = new DateDefinition { Horizon = "3m",Days=20};
            DateDefinition d6_0 = new DateDefinition { Horizon = "3m",Days=0};
        
            var definitions = new List<DateDefinition> {d6,d5,d4,d3,d2,d1,d1_0,d2_0,d3_0,d4_0,d5_0,d6_0};
        
            definitions.Sort();
        
            foreach(var d in definitions)
            {
                Console.WriteLine(d.Id);
            } 
        }
    }

    public class DateDefinition : IComparable<DateDefinition>
    {
        public string Horizon { get; set; }
        public int Days { get; set; }
        public string Id
        {
            get { return Horizon + "_" + Days.ToString(); }
        }
        
        public int CompareTo(DateDefinition other)
        {
            if (ReferenceEquals(other, this)) return 0;
            if(ReferenceEquals(other,null)) return -1;
            return string.Compare(Id,other.Id, StringComparison.InvariantCultureIgnoreCase);
        }
    }    
}

Output to the above code:

0d_0 0d_20 10d_0 10d_20 1m_0 1m_20 2m_0 2m_20 3m_0 3m_20 5d_0 5d_20

Need the output like:

0d_0 0d_20 5d_0 5d_20 10d_0 10d_20 1m_0 1m_20 2m_0 2m_20 3m_0 3m_20

Important Note: Please note that d=days , m=months in the above context.

1
  • What does Horizon = "5d",Days=20 mean? 5d seems to imply 5 days, but then what does Days=20 mean? Commented Oct 15, 2022 at 15:41

3 Answers 3

2

Inside the CompareTo method, you are using a string.Compare so it's comparing the calculated Id properties of the two instances alphabetically. Alphabetically "10d_0" comes before "5d_0".

You want to do some kind of numerical comparison, but you will need something to translate Horizon values stated in months into values stated in days (or vice-versa) to do a valid comparison.

It seems a little awkward defining the Horizon property as a string because it seems to contain a number and units (d for days or m for months). You will either need to parse those out and convert to numbers, or re-define Horizon into a numeric HorizonValue and a string HorizonUnits.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks jeff. Horizon property is a string as its a business requirement to denote it in such a way and do some further calclulations based on that.
0

You need CompareTo() to implement your custom business rule. As I understand it, your algorithm would be:

public int CompareTo(DateDefinition other)
{
    // I'm using the C# 8 range operator. Equivalent to Horizon.Length-1.
    var thisTimeUnit = Horizon[^1]; 
    var otherTimeUnit = other.Horizon[^1];
    
    // See if the time units are unequal. If there are more than 2 time units, you'll
    // want to associate a rank order with each and compare the rank orders.
    if (thisTimeUnit == 'm' && otherTimeUnit == 'd')
        return 1;
    else if (thisTimeUnit == 'd' && otherTimeUnit == 'm')
        return -1;

    // Sort by the numeric value of horizon if they are different
    var thisHorizon = int.Parse(Horizon[0..^1]);
    var otherHorizon = int.Parse(other.Horizon[0..^1]);
    
    if (thisHorizon != otherHorizon)
        return thisHorizon - otherHorizon;
    
    // If we get here, we have to sort by Days because Horizon is the same
    return Days - other.Days;
}

Comments

0

The class DateDefinition must implement IComparable<DateDefinition> for sorting to work as expected.

In the following example I add some logic to the Horizon property. The setter splits the passed string into a number and a unit and stores them in the fields char? _horizonUnit; int? _horizonNumber;;

The getter combines these parts to recreate the original string.

public class DateDefinition : IComparable<DateDefinition>
{
    private char? _horizonUnit;
    private int? _horizonNumber;

    public string Horizon
    {
        get => _horizonNumber.HasValue ? _horizonNumber.ToString() + _horizonUnit : null;
        set {
            if (String.IsNullOrEmpty(value)) {
                _horizonUnit = null;
                _horizonNumber = null;
            } else {
                _horizonUnit = value[^1];
                if (Int32.TryParse(value[..^1], out int i)) {
                    _horizonNumber = i;
                } else {
                    _horizonNumber = null;
                    _horizonUnit = null;
                    throw new ArgumentException("Horizon does not contain a valid number");
                }
            }
        }
    }

    public int Days { get; set; }

    public string Id => $"{Horizon}_{Days}";

    public override string ToString() => Id;

    public int CompareTo(DateDefinition? other)
    {
        if (other == null) {
            return 1;
        } else {
            int comp = (_horizonUnit ?? (char)0).CompareTo(other._horizonUnit ?? (char)0);
            if (comp != 0) {
                return comp;
            }
            comp = (_horizonNumber ?? 0).CompareTo(other._horizonNumber ?? 0);
            if (comp != 0) {
                return comp;
            }
            return Days.CompareTo(other.Days);
        }
    }
}

ComparesTo returns -1 if this is less than other, 0 if both are equal and +1 if this is greater then other.

First it compares the units. It happens that 'd' is less than 'm', so, the built-in Char.CompareTo returns the desired order. If the units are equal, we compare the horizon numbers. If those are equal as well we compare the Days.

Note that I have overridden ToString to let it return Id. So, you can simply write Console.WriteLine(d); now. This is also better for debugging, as the debugger now displays the Id for DateDefinition objects.

Note also that I have used the new Indexes and Ranges feature of C#. If you cannot use them, you can use String.Subtring instead (it's a bit more cumbersome).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.