C# 对列表进行排序,同时还返回原始索引位置?

问题描述 投票:0回答:5

我对对集合进行排序感兴趣,但也返回一个索引,该索引可用于映射到集合中的原始位置(排序之前)。

让我举个例子更清楚:

List<int> A = new List<int>(){3,2,1};
List<int> B;
List<int> idx;

Sort(A,out B,out idx);

之后:

A = [3,2,1] 
B = [1,2,3]
idx = [2,1,0]

那么A,B,idx之间的关系为:

A[i] == B[ idx[i] ]
,对于 i = 0...2

C#/.Net 是否有任何内置机制可以使其易于实现?

谢谢。

c# .net sorting collections
5个回答
64
投票

使用 Linq 可以很容易地完成。

  • 将您的列表转换为新的对列表(对象,对象的原始索引)。
  • 按对中的第一项对新列表进行排序
  • 提取排序列表和原始索引。

下面是一些代码来演示原理:

List<int> A = new List<int>() { 3, 2, 1 };

var sorted = A
    .Select((x, i) => new KeyValuePair<int, int>(x, i))
    .OrderBy(x => x.Key)
    .ToList();

List<int> B = sorted.Select(x => x.Key).ToList();
List<int> idx = sorted.Select(x => x.Value).ToList();

我认为这给出了 A[idx[i]] = B[i],但这对你来说已经足够好了。


26
投票

虽然 Mark Byers 为您提供了一个使用 LINQ 的解决方案,但我想向您展示另一个使用 .NET Framework 的解决方案。

有一个超载的

Array.Sort
可以为你做到这一点:

int[] a = new[] { 3, 2, 1 };
int[] p = new[] { 0, 1, 2 };

Array.Sort(a, p);

Assert.IsTrue(a.SequenceEquals(new[] { 1, 2, 3 }));
Assert.IsTrue(p.SequenceEquals(new[] { 2, 1, 0 }));

因此,这是一个满足您的规范的通用方法,它利用了此重载:

void Sort<T>(
    List<T> input,
    out List<T> output,
    out List<int> permutation,
    IComparer<T> comparer
) {
    if(input == null) { throw new ArgumentNullException("input"); }
    if(input.Count == 0) {
        // give back empty lists
        output = new List<T>(); 
        permutation = new List<int>();
        return;
    }
    if(comparer == null) { throw new ArgumentNullException("comparer"); }
    int[] items = Enumerable.Range(0, input.Count).ToArray();
    T[] keys = input.ToArray();
    Array.Sort(keys, items, comparer);
    output = keys.ToList();
    permutation = items.ToList();   
}

8
投票

使用 lambda 的更优雅的方法

Array.Sort<int>(idx, (a, b) => A[a].CompareTo(A[b]));

这从 A 数组中给出了 u idx 数组


1
投票

目前,您还可以使用匿名类型或值元组来代替

KeyValuePair
。它将提供更精确的命名并使您的代码更具可读性:

匿名类型(C# 3.0):

List<int> arr = new List<int>() { 3, 2, 1 };

var sorted = arr
    .Select((x, i) => new { Value = x, OriginalIndex = i }))
    .OrderBy(x => x.Value)
    .ToList();

int originalIndexOfTheSmallestItem = sorted[0].OriginalIndex;

List<int> B = sorted.Select(x => x.Value).ToList();
List<int> idx = sorted.Select(x => x.OriginalIndex).ToList();

值元组 (C# 7.0):

List<int> arr = new List<int>() { 3, 2, 1 };

var sorted = arr
    .Select((x, i) => (Value: x, OriginalIndex: i))
    .OrderBy(x => x.Value)
    .ToList();

int originalIndexOfTheSmallestItem = sorted[0].OriginalIndex;

List<int> B = sorted.Select(x => x.Value).ToList();
List<int> idx = sorted.Select(x => x.OriginalIndex).ToList();   

不同之处在于,您可以从方法中返回值元组并使用它,但匿名类型只能在该方法中使用。


0
投票

我通常会在需要时编写特定的函数。不太通用但更简单。例如:

static int[] ArgSort(List<double> lst)
{
  int n = lst.Count;
  int[] idxs = new int[n];
  for (int i = 0; i < n; ++i)
    idxs[i] = i;
  double[] arr = lst.ToArray();
  Array.Sort(arr, idxs);  // sort idxs based on arr vals
  return idxs;
}
© www.soinside.com 2019 - 2024. All rights reserved.