.NET, правильно сортирующая двойные строки - PullRequest
4 голосов
/ 10 февраля 2012

У меня есть приложение, которое отображает набор данных, который позволяет мне вызывать пользовательский код .NET, и я застрял в проблеме сортировки. Один столбец в моем наборе данных содержит как строки, так и числовые данные, и я хочу отсортировать строки по алфавиту и по номерам. Все, что я могу сделать, это взять текущее значение, с которым работает сортировщик, и вернуть что-то.

Если мой список равен {"-6", "10", "5"}, я хочу создать строки из тех чисел, которые отсортировали бы их по алфавиту. Я пришел к выводу, что все они должны быть положительными, а затем дополнены нулями, например:

public object Evaluate(object currentValue)
{
    //add 'a' to beginning of non-numbers, 'b' to beginning of numbers so that numbers come second
    string sortOrder = "";
    if(!currentValue.IsNumber)
        sortOrder = "a" + currentValue;
    else
    {
        sortOrder = "b"
        double number = Double.Parse(currentValue);

        //add Double.MaxValue to our number so that we 'hopefully' get rid of negative numbers, but don't go past Double.MaxValue
        number += (Double.MaxValue / 2)

        //pad with zeros so that 5 comes before 10 alphabetically:
        //"0000000005"
        //"0000000010"
        string paddedNumberString = padWithZeros(number.ToString())


        //"b0000000005"
        //"b0000000010"
        sortOrder += paddedNumberString;
    }
}

Проблема:
Если я просто верну число, то они будут отсортированы в алфавитном порядке, и перед 5 появится 10, и я даже не знаю, что будет с отрицательными числами.

Решение:
Один хак, о котором я думал, пытался преобразовать из двойных (8 байт) в беззнаковые длинные (8 байт). Это избавило бы от отрицательных чисел, так как они начинались бы с 0. Однако проблема 10, предшествующая 5, все еще остается. Для этого, возможно, pad с 0 или что-то ...

Кажется, это должно быть возможно, но у меня сегодня тупой и не умный.

пример данных:
'Кошка'
'4'
'5,4'
'Собака'
'-400'
'Муравьед'
'12 .23.34.54'
«Я - предложение»
'0'

, которые следует отсортировать по:
'12 .23.34.54'
'Муравьед'
'Кошка'
'Собака'
«Я - предложение»
'-400'
'0'
'4'
'5,4'

Ответы [ 4 ]

4 голосов
/ 10 февраля 2012

Не очень эффективный, но простой алгоритм сравнения, который сначала разделяет числа и не числа, а затем сортирует их между собой - см. Код ниже. Недостаток происходит из-за того, что мы сделаем строку для двойного преобразования довольно много раз, поэтому вы можете выполнить предварительную обработку чисел (т.е. сохранить их двойные значения в List<double?>), а затем использовать их вместо всегда делаю разбор.

public class StackOverflow_9231493
{
    public static void Test()
    {
        List<string> list = new List<string>
        {
            "cat",
             "4",
             "5.4",
             "dog",
             "-400",
             "aardvark",
             "12.23.34.54",
             "i am a sentence",
             "0" ,
        };

        list.Sort(new Comparison<string>(delegate(string s1, string s2)
        {
            double d1, d2;
            bool isNumber1, isNumber2;
            isNumber1 = double.TryParse(s1, out d1);
            isNumber2 = double.TryParse(s2, out d2);
            if (isNumber1 != isNumber2)
            {
                return isNumber2 ? -1 : 1;
            }
            else if (!isNumber1)
            {
                return s1.CompareTo(s2);
            }
            else
            {
                return Math.Sign(d1 - d2);
            }
        }));

        Console.WriteLine(string.Join("\n", list));
    }
}

Обновление на основе комментариев :

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

public class StackOverflow_9231493
{
    public class Wrapper : IComparable<Wrapper>
    {
        internal string value;
        private double? dbl;

        public Wrapper(string value)
        {
            if (value == null) throw new ArgumentNullException("value");
            this.value = value;
            double temp;
            if (double.TryParse(value, out temp))
            {
                dbl = temp;
            }
        }

        public int CompareTo(Wrapper other)
        {
            if (other == null) return -1;
            if (this.dbl.HasValue != other.dbl.HasValue)
            {
                return other.dbl.HasValue ? -1 : 1;
            }
            else if (!this.dbl.HasValue)
            {
                return this.value.CompareTo(other.value);
            }
            else
            {
                return Math.Sign(this.dbl.Value - other.dbl.Value);
            }
        }
    }
    public static void Test()
    {
        List<string> list = new List<string>
        {
            "cat",
             "4",
             "5.4",
             "dog",
             "-400",
             "aardvark",
             "12.23.34.54",
             "i am a sentence",
             "0" ,
        };

        List<Wrapper> list2 = list.Select(x => new Wrapper(x)).ToList();
        list2.Sort();
        Console.WriteLine(string.Join("\n", list2.Select(w => w.value)));
    }
}
2 голосов
/ 11 февраля 2012

У меня есть решение для вас, но оно требует произвольного фиксированного максимального размера строки, но не требует никакой другой информации о наборе

Сначала определите пользовательский набор символов следующим образом:

public class CustomChar
{
    public static readonly int Base;
    public static readonly int BitsPerChar;

    public char Original { get; private set; }
    public int Target { get; private set; }

    private static readonly Dictionary<char, CustomChar> Translation;

    private static void DefineOrderedCharSet(string charset)
    {
        foreach (var t in charset)
        {
            new CustomChar(t);
        }
    }

    static CustomChar()
    {
        Translation = new Dictionary<char, CustomChar>();
        DefineOrderedCharSet(",-.0123456789 aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ");
        BitsPerChar = (int)Math.Ceiling(Math.Log(Translation.Count, 2));
        Base = (int) Math.Pow(2, BitsPerChar);
    }

    private CustomChar(char original)
    {
        Original = original;

        if(Translation.Count > 0)
        {
            Target = Translation.Max(x => x.Value.Target) + 1;
        }
        else
        {
            Target = 0;
        }

        Translation[original] = this;
    }

    public static CustomChar Parse(char original)
    {
        return Translation[original];
    }
}

Затем определите конструкцию для обработки преобразования из строки в System.Numeric.BigInteger следующим образом

public class CustomString
{
    public string String { get; private set; }
    public BigInteger Result { get; private set; }
    public const int MaxChars = 600000;

    public CustomString(string source)
    {
        String = source;
        Result = 0;

        for (var i = 0; i < String.Length; i++)
        {
            var character = CustomChar.Parse(String[i]);
            Result |= (BigInteger)character.Target << (CustomChar.BitsPerChar * (MaxChars - i - 1));
        }

        double doubleValue;

        if (!double.TryParse(source, out doubleValue))
        {
            return;
        }

        Result = new BigInteger(0x7F) << (MaxChars * CustomChar.BitsPerChar);
        var shifted = (BigInteger)(doubleValue * Math.Pow(2, 32));
        Result += shifted;
    }

    public static implicit operator CustomString(string source)
    {
        return new CustomString(source);
    }
}

Обратите внимание, что ctor для CustomString находит дубликаты и увеличивает их BigInteger представления, чтобы организовать вещи длясортировка по числовому значению.

Это довольно быстрая сборка, но вы получите описанные результаты теста:

class Program
{
    public static string[] Sort(params CustomString[] strings)
    {
        return strings.OrderBy(x => x.Result).Select(x => x.String).ToArray();
    }

    static void Main()
    {
        var result = Sort(
            "cat",
            "4",
            "5.4",
            "dog",
            "-400",
            "aardvark",
            "12.23.34.54",
            "i am a sentence",
            "0");

        foreach (var str in result)
        {
            Console.WriteLine(str);
        }

        Console.ReadLine();
    }
}
1 голос
/ 10 февраля 2012

Я подозреваю, что вы ищете что-то под названием «Порядок естественной сортировки». У Этвуда есть пост: http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html

В посте есть пара примеров реализаций.

0 голосов
/ 10 февраля 2012

Я предполагаю, что ваши данные имеют тип string, а не object.Следующая функция может быть вызвана с Comparison<string> делегатом .

static int CompareTo(string string1, string string2)
{
    double double1, double2;

    // Add null checks here if necessary...

    if (double.TryParse(string1, out double1))
    {
        if (double.TryParse(string2, out double2))
        {
            // string1 and string2 are both doubles

            return double1.CompareTo(double2);
        }
        else
        {
            // string1 is a double and string2 is text; string2 sorts first

            return 1;
        }
    }
    else if (double.TryParse(string2, out double2))
    {
        // string1 is text and string2 is a double; string1 sorts first

        return -1;
    }
    else
    {
        // string1 and string2 are both text

        return string1.CompareTo(string2);
    }
}

. Вы можете проверить это так:

static void Main(string[] args)
{
    var list = new List<string>() {
        "cat",
        "4",
        "5.4",
        "dog",
        "-400",
        "aardvark",
        "12.23.34.54",
        "i am a sentence",
        "0"
    };

    list.Sort(CompareTo);
    foreach (var item in list)
        Console.WriteLine(item);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...