Получать новые индексы элементов в коллекции после сортировки с использованием LINQ
Я хочу отсортировать список (или массив) в С# и сохранить новые индексы для каждого из элементов в несортированном списке. То есть:.
A = 2 3 1
sorted(A) = 1 2 3
indexes = 1 2 0 <-- This is what I need
Я читаю о переполнении стека, что LINQ особенно полезно, но я не могу найти, как это сделать конкретно.
Ответы
Ответ 1
Примечание:
Я совершил ужасную ошибку из-за неправильного толкования вопроса OP (и смутил это, получив так много ударов). Я хотел (в идеале) OP принимает один из других ответов как правильный. Чтобы отдать должное сторонникам и духу SO, я исправлю этот ответ, чтобы сделать его правильным.
Можно использовать два метода расширения: один для старых индексов и один для новых индексов (чего хочет OP).
public static IEnumerable<int> OldIndicesIfSorted<T>(this IEnumerable<T> source) where T : IComparable<T>
{
return source
.Select((item, index) => new { item, index })
.OrderBy(a => a.item)
.Select(a => a.index);
}
public static IEnumerable<int> NewIndicesIfSorted<T>(this IEnumerable<T> source) where T : IComparable<T>
{
return source
.OldIndicesIfSorted()
.Select((oldIndex, index) => new { oldIndex, index })
.OrderBy(a => a.oldIndex)
.Select(a => a.index);
}
Назовите его,
//A = [2, 3, 1];
A.NewIndicesIfSorted(); // should print [1, 2, 0]
Два метода ленивы, но NewIndicesIfSorted
в идеале должен быть написан в соответствии с ответом Matthew Watson, который просто более эффективен. Положительная сторона как моего, так и ответного ответа Мата заключается в том, что он хорошо обрабатывает повторяющиеся записи.
Ответ 2
Вы можете сделать это с помощью LINQ select:
var a =new [] {2,3,1};
var sorted = a.OrderBy (x => x).ToList();
var indexes = a.Select (x => sorted.IndexOf(x));
Если это огромный список, а не простой, он может быть немного неэффективным, но возвращает то, что вы ожидаете.
Ответ 3
Один из способов сделать это:
int[] a = {2, 3, 1};
int[] b = Enumerable.Range(0, a.Length).ToArray();
int[] c = new int[a.Length];
Array.Sort(a, b);
for (int i = 0; i < b.Length; ++i)
c[b[i]] = i;
Console.WriteLine(string.Join(", ", c)); // Prints 1, 2, 0
Он использует перегрузку Array.Sort()
, которая сортирует один массив, используя значения из другого массива.
Я думаю, что это довольно эффективно: он использует сортировку O (N log (N)) и переназначение O (N). (Другими правильными решениями являются O (N log (N)) плюс O (N ^ 2))
Он работает только с массивами (потому что для списка нет эквивалента Array.Sort()
).
Альтернатива, использующая Linq (и эффективная поправка к ответу @nawfal):
int[] a = {2, 3, 1};
var b = a
.Select((item, index) => new { item, index })
.OrderBy(x => x.item)
.Select(x => x.index)
.ToArray();
int[] c = new int[b.Length];
for (int i = 0; i < b.Length; ++i)
c[b[i]] = i;
Console.WriteLine(string.Join(", ", c)); // Prints 1, 2, 0
Важным моментом здесь является дополнительный шаг отображения в обоих подходах:
int[] c = new int[b.Length];
for (int i = 0; i < b.Length; ++i)
c[b[i]] = i;
Это действительно обратное отображение.
Ответ 4
Другое рабочее решение:
var input = new List<int> { 2, 3, 1 };
var result = input.Select(x => input.OrderBy(n => n).ToList().IndexOf(x));
Console.WriteLine(String.Join(" ", result)); // 1 2 0
Ответ 5
При чтении других ответов я немного смутился, о каком indexes
заказе вы хотите точно, но вот решение, если вы хотите, чтобы они были в порядке 1 2 0
, как вы разместили в своем вопросе. Попробуйте следующее:
var A = new int[] { 2, 3, 1 };
var indexes = new int [A.Length];
var sorted = A.OrderBy(n => n)
.Select((n, i) =>
{
indexes[Array.IndexOf(A, n)] = i;
return n;
})
.ToArray();