Pull to refresh

Comments 12

Раз lstrcmp ошибается на сравнении

Понимаю, что вы понимаете, но звучит странно, ибо он ни в коем разе не ошибается, просто реализует другую (базовую) логику :)

В очередной раз спасибо, что поддерживаете Delphi-контент на Хабре своими статьями. В свое время делал аналогичную реализацию, нужно было сортировать адреса (номера домов).
Понимаю, что вы понимаете, но звучит странно, ибо он ни в коем разе не ошибается, просто реализует другую (базовую) логику :)

Да, действительно, немного не удачная фраза :)
Но чуть дальше я постарался расписать подробнее о сути «ошибки».
Просто не знаю как правильно перестроить предложение в данном случае :)
В дельфи нет готовой библиотеки, реализующей natsort?
В базовой поставке нет.
Почему бы не использовать CompareNaturalText из JclStrings?

В этой же реализации меня смущает приведение к AnsiString'ам.
Почему бы не использовать CompareNaturalText из JclStrings?

Честно говоря не знал о CompareNaturalText, мы не используем Jedy.

В этой же реализации меня смущает приведение к AnsiString'ам.

Эмм, AnsiLowerCase не приводит к Ansi строке :)
Зависит от того, какой модуль SysUtils или AnsiStrings используется ;)

Вообще в Дельфи меня смущает (не в отношении вашего кода, здесь оно отработает верно) реализация charInSet для входного char:

var
  s: TSysCharSet;
  c: char;
  ac: AnsiChar;
begin
  s := ['я'];
  c := 'я';
  ac := 'я';
  if CharInSet(c, s) then
    Writeln('Ok') else
    Writeln('Fail');
  if c in s then
    Writeln('Ok') else
    Writeln('Fail');
  if ac in s then
    Writeln('Ok') else
    Writeln('Fail');


будет Fail, Fail, Ok под XE
Ох, это старая ошибка и (если не ошибаюсь) идет с самой первой юникодной D2009.
Впрочем проблемы с юникодом, к сожалению даже в XE3 не все поправлены, а уж сколько лет прошло.
Кстати нужно проверить вызов CharInSet под ХЕ4, уж больно сильный был на семинаре пиар по поводу исправления большинства ошибок. Чем бог не шутит — может действительно поправили :)
В одном проекте встала аналогичная задача (надо было сортировать номера домов, в которых были и дроби, и корпуса и номера строений). Но поскольку данные в список загружались из базы данных, то решил задачу средствами SQL. Сейчас сортировка осуществляется так: ORDER BY CAST((regexp_matches(housenum, '^\d*'))[1] AS INT). Мне кажется, так проще, хотя рассмотренный в статье способ, вероятно, более универсальный.
Оставлю здесь вариант для C#
смотреть
// yourobj.Sort((x, y) => StringLogicalComparer.Compare(x, y));      
public class StringLogicalComparer
    {
        public static int Compare(string s1, string s2)
        {
            //get rid of special cases
            if ((s1 == null) && (s2 == null)) return 0;
            else if (s1 == null) return -1;
            else if (s2 == null) return 1;

            if ((s1.Equals(string.Empty) && (s2.Equals(string.Empty)))) return 0;
            else if (s1.Equals(string.Empty)) return -1;
            else if (s2.Equals(string.Empty)) return -1;

            //WE style, special case
            bool sp1 = Char.IsLetterOrDigit(s1, 0);
            bool sp2 = Char.IsLetterOrDigit(s2, 0);
            if (sp1 && !sp2) return 1;
            if (!sp1 && sp2) return -1;

            int i1 = 0, i2 = 0; //current index
            int r = 0; // temp result
            while (true)
            {
                bool c1 = Char.IsDigit(s1, i1);
                bool c2 = Char.IsDigit(s2, i2);
                if (!c1 && !c2)
                {
                    bool letter1 = Char.IsLetter(s1, i1);
                    bool letter2 = Char.IsLetter(s2, i2);
                    if ((letter1 && letter2) || (!letter1 && !letter2))
                    {
                        if (letter1 && letter2)
                        {
                            r = Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2]));
                        }
                        else
                        {
                            r = s1[i1].CompareTo(s2[i2]);
                        }
                        if (r != 0) return r;
                    }
                    else if (!letter1 && letter2) return -1;
                    else if (letter1 && !letter2) return 1;
                }
                else if (c1 && c2)
                {
                    r = CompareNum(s1, ref i1, s2, ref i2);
                    if (r != 0) return r;
                }
                else if (c1)
                {
                    return -1;
                }
                else if (c2)
                {
                    return 1;
                }
                i1++;
                i2++;
                if ((i1 >= s1.Length) && (i2 >= s2.Length))
                {
                    return 0;
                }
                else if (i1 >= s1.Length)
                {
                    return -1;
                }
                else if (i2 >= s2.Length)
                {
                    return -1;
                }
            }
        }

        private static int CompareNum(string s1, ref int i1, string s2, ref int i2)
        {
            int nzStart1 = i1, nzStart2 = i2; // nz = non zero
            int end1 = i1, end2 = i2;

            ScanNumEnd(s1, i1, ref end1, ref nzStart1);
            ScanNumEnd(s2, i2, ref end2, ref nzStart2);
            int start1 = i1;
            i1 = end1 - 1;
            int start2 = i2;
            i2 = end2 - 1;

            int nzLength1 = end1 - nzStart1;
            int nzLength2 = end2 - nzStart2;

            if (nzLength1 < nzLength2) return -1;
            else if (nzLength1 > nzLength2) return 1;

            for (int j1 = nzStart1, j2 = nzStart2; j1 <= i1; j1++,j2++)
            {
                int r = s1[j1].CompareTo(s2[j2]);
                if (r != 0) return r;
            }
            // the nz parts are equal
            int length1 = end1 - start1;
            int length2 = end2 - start2;
            if (length1 == length2) return 0;
            if (length1 > length2) return -1;
            return 1;
        }

        //lookahead
        private static void ScanNumEnd(string s, int start, ref int end, ref int nzStart)
        {
            nzStart = start;
            end = start;
            bool countZeros = true;
            while (Char.IsDigit(s, end))
            {
                if (countZeros && s[end].Equals('0'))
                {
                    nzStart++;
                }
                else countZeros = false;
                end++;
                if (end >= s.Length) break;
            }
        }

Sign up to leave a comment.

Articles