Как отсортировать список сложных объектов в С#

Как я могу отсортировать List<object>, где объект всегда List<string>. Я знаю, что уже есть вопросы, похожие на этот. Я пробовал Sort() или другие предложенные функции, но не смог решить.

Вот что у меня есть:

var result= new List<object>();

foreach (var data in myData)
{
  result.Add(new List<string>() { data.Text, myData.Count().ToString() });                     
}
//This is how at the end the List<object> looks like:
[
    [
        "xxx",
        "2"
    ],
    [
        "xxxx",
        "2"
    ],
    [
        "xxx",
        "2"
    ],
    [
        "xxxx",
        "3"
    ],
    [
        "xxx",
        "48"
    ],
    [
        "xxxx"
        "18"
    ],
    [
        "xxxx",
        "58"
    ],
    [
        "xxxx",
        "1"
    ],
    [
        "xxxx",
        "371"
    ],
    [
        "xxxx",
        "3"
    ],
    [
        "xxxxx",
        "2"
    ]
]

Мне нужно упорядочить List<object> вторым элементом списка, который всегда числовой

К вашему сведению — вы почти никогда не захотите использовать object. Создайте новый тип для хранения того, что вы хотите, или используйте существующий тип, который соответствует варианту использования. KeyValuePair может подойти, поскольку в вашем примере похоже, что у вас есть дубликаты «ключей».

Broots Waymb 11.10.2022 16:06

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

Narish 11.10.2022 16:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
203
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Вы можете отсортировать список следующим образом:

data = data.OrderBy(i => int.Parse(i[1])).ToList();

Но мы можем сделать еще лучше, если пропустим промежуточный шаг в вопросе и начнем с myData:

var data = myData.Select(md => (md.Text, md.Count())).OrderBy(md => md.Item2);

Это выглядит немного иначе, чем то, что вы просили, но объективно лучше по двум причинам:

  1. Это позволяет нам сохранить значение int в более точном типе. Благодаря интернационализации/культурным проблемам преобразование между числами, датами и строками и обратно происходит гораздо медленнее и более подвержено ошибкам, чем нам хотелось бы верить. Поэтому этого следует избегать. Обычно гораздо лучше хранить специализированные числовые данные как можно дольше и преобразовывать их в строки только в последний момент. В этом случае мы дважды сохраняем операцию преобразования (по одному разу в каждом направлении), так что этот код уже должен быть НАМНОГО быстрее исходного.

  2. Мы получаем IEnumerable<(string, int)> вместо List<List<string>>. IEnumerable<T> почти всегда намного эффективнее, чем List<T>, из-за меньшего использования памяти и возможности потоковой передачи данных по мере их поступления, вместо того, чтобы загружать всю настройку заранее.

Однако я понимаю, что вам могут понадобиться данные определенным образом, чтобы передать их на следующий шаг. Если вам действительно нужен джанки List<List<string>>, вы можете сделать это:

var data = myData.OrderBy(md => md.Count()).
     Select(md => new List<string>{md.Name, md.Count().ToString()}).
     ToList();

Обратите внимание, что из-за вышеперечисленных моментов, даже когда нам действительно нужен список, наиболее эффективным способом его получения было сохранение исходного int для заказа и создание IEnumerable перед созданием окончательного списка, и в зависимости от того, сколько времени потребуется для запуска. Count() может быть даже эффективнее использовать мое основное предложение IEnumerable<(string, int)> в качестве промежуточного шага:

var data = myData.Select(md => (md.Text, md.Count())).
    OrderBy(md => md.Item2).
    Select(md => new List<string> {md.Item1, md.Item2.ToString()}).
    ToList();

Надеюсь, это также поможет вам по-другому взглянуть на этот вид обработки данных. Если IEnumerable с типизированными данными настолько эффективнее, что мы все равно используем его в качестве промежуточного шага, то, вероятно, это также лучший способ обработки передачи данных между методами или классами в первую очередь. Как минимум, вы можете хотя бы начать думать об удалении .ToList() с конца этих строк, так как вы всегда можете добавить его к переменной при передаче в функцию, которой все еще действительно нужен список.

Я бы порекомендовал вместо этого использовать строго типизированную структуру, очень быстрой здесь может быть кортеж, который теперь хорошо интегрирован в C# - может называть параметры. Или даже лучше создайте класс, применимый к вашему использованию.

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

var result = new List<(string Text, int Count)>()
      
foreach (var data in myData)
{
    result.Add(new ( data.Text, myData.Count()));                     
}

result.Sort((a,b) => a.Count - b.Count);
//here your list is already sorted

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

var sortedList = result.OrderBy((element) => element.Count)

Это чище, так как если вам не нужно работать с более сложными типами или выполнять вычисления, достаточно только указать, какое свойство вашего класса является ключом для использования для сортировки. Затем объекты будут возвращены в порядке, указанном значениями ключа, игнорируя остальную часть класса. Вы также можете передать более сложное выражение в качестве второго параметра, аналогично методу Sort.

Вы можете использовать удобные C# ValueTuples для простого создания строго типизированных данных.

var textCount = new List<(string Text, int Count)>();

foreach (var data in myData)
{
  textCount.Add( (data.Text, data.Count()) );
}
// In-place sort of list
textCount.Sort((a, b) => a.Count.CompareTo(b.Count));

Если вы хотите отсортировать в порядке убывания, просто отмените сравнение

textCount.Sort((a, b) => -a.Count.CompareTo(b.Count));

или сравните b с a:

textCount.Sort((a, b) => b.Count.CompareTo(a.Count));

Или с LINQ

var textCount = myData
    .Select(d => (d.Text, Count: d.Count()))
    .OrderBy(t => t.Count) // Use OrderByDescending for reverse sort
    .ToList();

// Test
foreach (var item in textCount) {
    Console.WriteLine($"Text = {item.Text}, Count = {item.Count}");
}

Оба решения сохраняют счетчик как int. Это важно для сортировки. Числа, хранящиеся в виде строки, не будут сортироваться правильно. Вы получите алфавитный порядок, например «1», «11», «157», «2», «27», «3», «32».

Преимущество второго решения заключается в создании кортежей, сортировке, создании и заполнении списка в одном операторе.

Я не вижу смысла вам работать с объектом, в начале вы можете сделать такую ​​​​фичу, как

        var result = new List<(string, string)>();


        result.OrderBy(x => x.Item2);

это облегчит вам заказ и добавление в список, подобный этому

foreach (var data in myData)
{
  result.Add((data.Text, myData.Count().ToString()));                     
}

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

var result = new List<(string, int)>();

result.OrderBy(x => x.Item2);

foreach (var data in myData)
{
  result.Add((data.Text, myData.Count().ToString()));                     
}

Я не рекомендую использовать только объект в виде списка из-за потери производительности, и если бы мне пришлось подумать об этом, мне пришлось бы создать абстрактный класс только для сравнения их списков и сортировки вручную.

Другие вопросы по теме