Математическое сравнение 2 чисел с несколькими десятичными дробями

#c#

Вопрос:

Мне нужно сравнить номера разделов в документе, сначала я просто собирался преобразовать их в десятичные и проверить, больше ли одно число другого. Проблема в том, что в некоторых разделах есть несколько десятичных знаков.

Пример: Мне нужно выполнить математическое сравнение 1.1 с 1.1.2.3, чтобы определить, какой из них находится дальше в документе.

Для начала это строки, и мне, по сути, нужно провести некоторые математические сравнения с ними. Я думал об удалении десятичных знаков, а затем преобразовании в int, но это отбрасывает некоторые разделы, например, раздел 2 будет считаться меньшим, чем раздел 1.1, поскольку 1.1 будет изменен на 11, что нехорошо.

     string section1 = "1.1";
    string section2 = "2";
    int convertedSection1 = Convert.ToInt32(section1.Replace(".",""));
    int convertedSection2 = Convert.ToInt32(section2.Replace(".",""));
    if(convertedSection1 < convertedSection2)
        //This will incorrectly say 1.1 is greater than 2

    string section1 = "1.1.2.4";
    string section2 = "2.4";
    decimal convertedSection1 = Convert.ToDecimal(section1);
    decimal convertedSection2 = Convert.ToDecimal(section2);
    if(convertedSection1 < convertedSection2)
        //This will convert 1.1.2.4 to 1.1 which is no good
 

Комментарии:

1. 1.1.2.4 должно быть преобразовано в 1.124?

2. //Это приведет к преобразованию 1.1.2.4 в 1.1, что не очень хорошо — чего вы здесь ожидаете?

Ответ №1:

Вы можете создать класс, аналогичный Version классу из .СЕТЕВАЯ структура. Если вы реализуете некоторые операторы и IComparable , это действительно приятно.

Как это работает? Он преобразует заданную строку в список целых чисел. При сравнении он будет начинаться в начале каждого списка и сравнивать каждую отдельную часть.

 public class Section: IComparable<Section>
{
    // Stores all individual components of the section
    private List<int> parts = new List<int>();

    // Construct a section from a string
    public Section(string section)
    {
        var strings = section.Split('.');
        foreach (var s in strings)
        {
            parts.Add(int.Parse(s));
        }
    }

    // Make it nice for display
    public override string ToString()
    {
        return  string.Join(".", parts);
    }

    // Implement comparison operators for convenience
    public static bool operator ==(Section a, Section b)
    {
        // Comparing the identical object
        if (ReferenceEquals(a, b)) return true;

        // One object is null and the other isn't
        if ((object)a == null) return false;
        if ((object)b == null) return false;

        // Different amount of items
        if (a.parts.Count != b.parts.Count) return false;

        // Check all individual items
        for (int i=0; i<a.parts.Count;i  )
        {
            if (a.parts[i] != b.parts[i]) return false;
        }

        return true;
    }
    public static bool operator !=(Section a, Section b)
    {
        return !(a == b);
    }

    public static bool operator <(Section a, Section b)
    {
        // Use minimum, otherwise we exceed the index
        for (int i=0; i< Math.Min(a.parts.Count, b.parts.Count); i  )
        {
            if (a.parts[i] < b.parts[i]) return true;
        }

        if (b.parts.Count > a.parts.Count) return true;
        return false;
    }

    public static bool operator >(Section a, Section b)
    {
        // Use minimum, otherwise we exceed the index
        for (int i = 0; i < Math.Min(a.parts.Count, b.parts.Count); i  )
        {
            if (a.parts[i] > b.parts[i]) return true;
        }

        if (a.parts.Count > b.parts.Count) return true;
        return false;
    }

    // Implement the IComparable interface for sorting
    public int CompareTo(Section other)
    {
        if (this == other) return 0;
        if (this < other) return -1;
        return 1;
    }
}
 

Тесты на покрытие 96% :

 Assert.IsTrue(new Section("1.2.3.4") > new Section("1.2.3"));
Assert.IsFalse(new Section("1.2.3.4") < new Section("1.2.3"));
Assert.IsFalse(new Section("1.2.3.4") == new Section("1.2.3"));
Assert.IsTrue(new Section("1.2.3.4") == new Section("1.2.3.4"));
Assert.IsFalse(new Section("1.2.3.4") == new Section("1.2.3.5"));
Assert.IsTrue(new Section("1.2.3.4") != new Section("1.2.3.5"));
var sec = new Section("1");
Assert.IsTrue(sec == sec);

Assert.AreEqual("1.2.3.4", new Section("1.2.3.4").ToString());

var sortTest = new List<Section> { new Section("2"), new Section("1.2"), new Section("1"), new Section("3.1") };
sortTest.Sort();
var expected = new List<Section> { new Section("1"), new Section("1.2"), new Section("2"), new Section("3.1") };
CollectionAssert.AreEqual(expected, sortTest, new SectionComparer());
 

Ответ №2:

Если вы знаете, что строки ваших разделов всегда хорошо сформированы, и вы знаете, что они не заходят глубже, чем на 6 уровней, и что ни один уровень не содержит более 999 элементов, то это хорошо работает:

 string zero = ".0.0.0.0.0.0";
long Section2Long(string section) =>
    (section   zero)
        .Split('.')
        .Take(6)
        .Select(t => long.Parse(t))
        .Aggregate((x, y) => x * 1000   y);
 

Теперь, если у меня есть это:

 string[] sections = new []
{
    "1.2.4", "2.3", "1", "1.2", "1.1.1.1", "1.0.0.1.0.1", "2.2.9"
};
 

Я легко могу разобраться в этом так:

 string[] sorted = sections.OrderBy(x => Section2Long(x)).ToArray();
 

Я получаю этот вывод:

 1 
1.0.0.1.0.1 
1.1.1.1 
1.2 
1.2.4 
2.2.9 
2.3