Сортировка списка массивов

У меня есть ArrayList, который содержит большое количество строк. Его необходимо отсортировать на месте по трем полям (фактически трем подстрокам): Имя, Возраст и Сумма. Возраст — первая подстрока (позиция 0–3), Имя — вторая (3–6) и Amt — последняя (6–10). . Порядок, в котором эти параметры должны быть отсортированы, очень важен и выглядит следующим образом:

Сначала выполните по возрастанию сортировку по Имени, ЗАТЕМ выполните по возрастанию сортировку по Возрасту (который на самом деле идет раньше в подстроке) и ТОГДА выполните сортировку по убыванию по Amt. Вот и все.

у меня есть этот класс

public class ArrComparer : IComparer
{
    public int Compare(object x, object y)
    {
        string left = x.ToString();
        string right = y.ToString();
        string lhs = left.Substring(3, 6);
        string rhs = right.Substring(3, 6);
        return lhs.CompareTo(rhs);
    }
}

который я использую для сортировки на основе только одного поля - имени, вызывая

RecordList.Sort(new ArrComparer());

Это позволяет мне правильно сортировать по этому одному полю. Вопрос в том, как я могу изменить этот код, чтобы я мог сортировать по всем трем элементам СРАЗУ, в правильном порядке и с использованием правильного режима возрастания/убывания?

Любой код или советы будут с благодарностью. (Кстати, если вам интересно, использование универсального List<T> не вариант в этом проекте).


person GonzoKnight    schedule 15.03.2011    source источник
comment
Почему общий список не вариант? Почему вы не используете объекты с тремя полями вместо строк?   -  person Matti Virkkunen    schedule 15.03.2011
comment
Не могли бы вы сказать, почему общие списки не подходят?   -  person John Saunders    schedule 15.03.2011
comment
Подумайте об этом. Как вы делаете обычную алфавитную сортировку? Вы сравните первые буквы. Если они разные, то вы знаете порядок. Если они равны, вы сравниваете вторые буквы. И так далее. То же самое здесь. Как сортировать по имени, потом по возрасту, потом по сумме? Сравните два имени. Они неравны? Тогда вы знаете порядок. Они равны? Тогда сравните возраст. Они неравны? Тогда вы знаете порядок. Они равны? Затем сравните суммы.   -  person Eric Lippert    schedule 15.03.2011
comment
Не могли бы вы сказать, почему общие списки не подходят? Это справедливый вопрос, и простой ответ заключается в том, что это небольшая часть гораздо более крупного проекта, который был написан некоторое время назад и опирается на существующие структуры данных. Изменить их на этом этапе будет сложнее, чем использовать то, что есть.   -  person GonzoKnight    schedule 15.03.2011
comment
Эрик, по сути это преобразование из COBOL, так результаты сортировались в оригинальной программе. Кроме того, LINQ является вариантом, если его можно использовать здесь. На самом деле я мало что знаю об этом на данный момент. Спасибо всем ответившим, кстати.   -  person GonzoKnight    schedule 15.03.2011


Ответы (5)


Если вам нужен IComparer, попробуйте что-то вроде:

public class ArrComparer : IComparer
{
  public int Compare(object x, object y)
  {
    string left = x.ToString();
    string right = y.ToString();
    string leftName = left.Substring([whatever]);
    string rightName = right.Substring([whatever]);

    // First try comparing names
    int result = leftName.CompareTo(rightName);
    if (result != 0)
    {
      return result;
    }

    // If that didn't work, compare ages
    string leftAge = left.Substring([whatever]);
    string rightAge = right.Substring([whatever]);
    result = leftAge.CompareTo(rightAge);
    if (result != 0)
    {
      return result;
    }    

    // Finally compare amounts (descending)
    string leftAmt = left.Substring([whatever]);
    string rightAmt = right.Substring([whatever]);
    result = -leftAmt.CompareTo(rightAmt); // Minus for descending

    return result;
  }
}
person Jeremy Todd    schedule 15.03.2011

ArrayList по-прежнему реализует IEnumerable, то есть вы можете использовать простые расширения orderby() и thenby() в linq:

RecordList = new ArrayList(
         RecordList.Cast<string>().OrderBy(s => s.Substring(3,3))
                   .ThenBy(s => int.Parse(s.Substring(0,3)))
                   .ThenByDescending(s => double.Parse(s.Substring(6,4)))
          .ToArray());

Другие способы выразить это включают создание более сложного .OrderBy() или использование анонимного типа для составления вашей строки как объекта:

RecordList = new ArrayList(
       Record.Cast<string>().Select(s => new {source = s, age = int.Parse(s.Substring(0, 3)), name = s.Substring(3,3), amt = double.Parse(s.Substring(6,4))})
             .OrderBy(o => o.name)
             .ThenBy(o => o.age)
             .ThenByDescending(o => o.amt)
          .Select(o => o.source).ToArray());

Мне нравится этот вариант, потому что он заставляет вас начать думать в терминах объектов. Правильно разыграйте свои карты, и вы сможете пропустить последнюю проекцию .Select(), чтобы сохранить объекты, а не возвращаться к строкам, что избавит от необходимости выполнять весь этот синтаксический анализ позже.

Если это не вариант (возможно, по той же причине, по которой вы не можете использовать List‹T>), можно легко изменить существующий метод сравнения следующим образом:

public class ArrComparer : IComparer
{
    public int Compare(object x, object y)
    {
        int result;
        string left = x.ToString();
        string right = y.ToString();
        string lhs1 = left.Substring(3, 3);
        string rhs1 = right.Substring(3, 3);
        result = lhs1.CompareTo(rhs1);

        if (result == 0)
        {
           int lhs2 = int.Parse(left.Substring(0,3));
           int rhs2 = int.Parse(right.Substring(0,3));
           result = lhs2.CompareTo(rhs2);
        }

        if (result == 0)
        {
            double lhs3 = double.Parse(left.Substring(6,4));
            double rhs3 = double.Parse(right.Substring(6,4));
            result = rhs3.CompareTo(lhs3);
        }

        return result;
    }
}
person Joel Coehoorn    schedule 15.03.2011
comment
это не будет выполнять сортировку на месте, хотя это требуется в вопросе ОП - person BrokenGlass; 15.03.2011
comment
@Joel: вы должны использовать Substring(3, 3) вместо Substring(3, 6), так как вторым аргументом является длина. То же самое для Substring(6, 10). - person Vlad; 15.03.2011
comment
@Влад, конечно, ты прав. В свою защиту я брал свою очередь из кода в исходном вопросе. - person Joel Coehoorn; 15.03.2011
comment
@Joel: кроме того, должна быть возможность опустить int.Parse (по крайней мере, внутри Compare(...)), потому что цифры возраста дополняются слева до одинакового количества десятичных знаков. Ну а с другой стороны разбор иллюстрирует возможность дальнейших трансформаций, так что он дидактически оправдан. - person Vlad; 15.03.2011
comment
@Vlad - даже если бы мне это не нужно, я бы оставил там int.Parse(), если только не будет показано, что это приводит к реальному снижению производительности, или мы начали видеть неправильно отформатированную строку (исключения), так как это помогает будущие программисты знают, что ожидается в этой позиции строки. - person Joel Coehoorn; 15.03.2011
comment
@Joel: структура данных в любом случае неуместна. struct было бы намного лучше. (И он также может реализовать IComparable.) - person Vlad; 15.03.2011
comment
@Vlad - вопрос не проясняет, но на самом деле структура данных очень подходит для других ее применений, таких как возможность вставлять новые строки и возможность перебирать весь объект. - person GonzoKnight; 15.03.2011
comment
@GonzoKnight: я имел в виду просто заменить string специальным struct, например struct Entry { int age; string name; string amt; } . Таким же образом вы сможете вставлять строки или перебирать весь список. Более того, я бы попытался заменить ArrayList на более типобезопасный List<Entry>, если это возможно. На самом деле, вы можете увидеть подобную анонимную структуру в подходе Linq. - person Vlad; 15.03.2011
comment
@ Влад - спасибо за разъяснение - я учту ваше предложение для будущих проектов. - person GonzoKnight; 16.03.2011
comment
@Joel Coehoorn - мне было бы очень любопытно попробовать приведенные выше примеры кода LINQ, которые вы написали. Однако оба они дают им ошибки компиляции, говорящие о том, что компилятор не может определить тип объекта и запрашивает явные типы. Я попытался изменить s на строку s, но это только создало больше проблем. Любые идеи, что еще я могу попробовать. В очередной раз очень помог ваш ответ. Спасибо! - person GonzoKnight; 16.03.2011
comment
@Gonzo - я забыл вызовы .ToString() во втором примере. Это то, что я получаю за ввод прямо в окно ответа. Не уверен, просто смотрю, что не так с первым. Какую версию Visual Studio/.Net вы используете? - person Joel Coehoorn; 16.03.2011
comment
Ладно, ладно, понадобилось несколько минут в Visual Studio, чтобы починить семплы. Теперь должно быть все хорошо. - person Joel Coehoorn; 16.03.2011
comment
@Joel Coehoorn - Большое спасибо за ваши ответы. Запросы LINQ теперь работают и помогли продемонстрировать, как их можно использовать. - person GonzoKnight; 16.03.2011

Вы можете сравнить по частям:

string left = (string)x;
string right = (string)y;

string lname = left.Substring(3, 3);
string rname = right.Substring(3, 3);
int result = lname.CompareTo(rname);
if (result != 0) return result;

string lage = left.Substring(0, 3);
string rage = right.Substring(0, 3);
int result = lage.CompareTo(rage);
if (result != 0) return result;

string lamt = left.Substring(6);
string ramt = right.Substring(6);
return -lamt.CompareTo(ramt);
person Vlad    schedule 15.03.2011

Я бы рекомендовал хранить ваши записи в объекте и вместо этого делать их сопоставимыми.

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

public class ArrComparer : IComparer
{
    public int Compare(object x, object y)
    {
        string left = x.ToString();
        string right = y.ToString();

        // Note I assumed indexes since yours were overlapping.
        string lage = left.Substring(0, 3);
        string lname = left.Substring(3, 3);
        string lamt = left.Substring(7, 3);

        string rage = left.Substring(0, 3);
        string rname = left.Substring(3, 3);
        string ramt = left.Substring(7, 3);

        // Compare name first, if one is greater return
        int result = lname.CompareTo(rname);
        if (result != 0)
            return result;

        // else compare age, if one is greater return
        result = lage.CompareTo(rage)
        if (result != 0)
            return result;

        // else compare amt if one is greater return
        result = lamt.CompareTo(ramt)
        if (result != 0)
            return result;

        // else they are equal
        return 0;
    }
}
person Cody    schedule 15.03.2011
comment
вы должны использовать Substring(3, 3) вместо Substring(3, 6), так как вторым аргументом является длина. То же самое для Substring(7, 10). Кроме того, Substring(0, 2) кажется неправильным. - person Vlad; 15.03.2011
comment
@Влад Ой! Спасибо за внимание. :) - person Cody; 15.03.2011
comment
Спасибо за все ответы, ребята! Мне понадобится немного времени, чтобы проанализировать их и опробовать, а затем я вернусь с отзывами. - person GonzoKnight; 15.03.2011

вы можете использовать ArrCompare с операторами if, такими как if(rhs == lhs), совпадающими с другой частью строки. Accen deccen - это счетчик возврата -1 или 1

person kalvis    schedule 15.03.2011