Получать новые индексы элементов в коллекции после сортировки с использованием 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();