【问题标题】:C# Sort list while also returning the original index positions?C#排序列表同时还返回原始索引位置?
【发布时间】:2010-12-18 03:02:31
【问题描述】:

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

让我举个例子更清楚:

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


【解决方案1】:

使用 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],但希望这对你来说已经足够了。

【讨论】:

    【解决方案2】:

    虽然 Mark Byers 使用 LINQ 为您提供了 solution,但我想向您展示另一个使用 .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();   
    }
    

    【讨论】:

    • 如果comparernull,我建议使用Comparer&lt;T&gt;.Default 而不是ArgumentNullExcpetion。否则,+1 以获得完整、直接和高效的解决方案。
    • @P Daddy:很好的建议,虽然我会通过提供一个重载Sort&lt;T&gt;(List&lt;T&gt;, out List&lt;T&gt;, out List&lt;int&gt;) 来处理这个问题,它使用Comparer&lt;T&gt;.Default 作为comparer 调用上述方法。
    • 我发现这个问题是因为我想对一个数组进行排序以获取索引,但我只是使用索引以相同的方式重新排序不同的数组。我很高兴发现我可以改用Array.Sort(arr1, arr2);;谢谢!
    【解决方案3】:

    一种使用 lambda 的更优雅的方法

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

    这给出了来自 A 数组的 u idx 数组

    【讨论】:

    • idx 必须是一个数组,由Enumerable.Range(0, A.Count).ToArray() 预先分配,而不是一个空列表。为了完成您的回答,B = A.OrderBy(i =&gt; Array.IndexOf(idx, A.IndexOf(i))).ToList();
    【解决方案4】:

    就目前而言,您还可以使用匿名类型或值元组来代替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();   
    

    不同的是你可以从你的方法返回值元组并使用它,但是匿名类型只能在这个方法中使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 2016-05-20
      • 2011-12-12
      相关资源
      最近更新 更多