.NET 中有一组特定的内置函数可以执行此操作。寻找带有TKey[] 参数的Array.Sort 的重载。有几个重载可让您指定要排序的子范围,或自定义IComparer<TKey>。秘诀是将原始数组作为keys 参数传递,并为items 参数传递一个身份数组(0, 1, 2,... n-1)。以下函数将为您完成所有工作:
/// sort array 'rg', returning the original index positions
static int[] SortAndIndex<T>(T[] rg)
{
int i, c = rg.Length;
var keys = new int[c];
if (c > 1)
{
for (i = 0; i < c; i++)
keys[i] = i;
System.Array.Sort(rg, keys /*, ... */);
}
return keys;
}
同样,对于Array.Sort,请注意我们要小心可能令人困惑的参数名称。我们将 items 作为第一个参数(称为“keys”)传入,而我们的 index-to-be(感觉更像键)作为第二个参数(称为“items”)。
用法不言自明:
var rgs = new[] { "xyz", "a", "", "bb", "pdq" };
int[] idx = SortAndIndex(rgs); // rgs: { "", "a", "bb", "pdz", "xyz" }
// idx: { 2, 1, 3, 4, 0 }
这涵盖了 OP 的情况,您实际上希望原始数据最终排序。如果这是您需要的,您可以在此处停止阅读。
但是一个相关的问题是,如果你想要那些相同的排序指标,但你不想修改原始数组怎么办?我们如何获得排序索引而不改变原始项目的顺序?
我发现做到这一点的最佳方法实际上是使用上述过程对数据进行排序并获取索引,然后使用该排序索引将已排序的项目恢复为原始订单。
可能有几种方法可以做到这一点,但由于这个问题提到了效率,我可以展示一些保证执行最少数量的原始项目交换的代码,同时只使用一个 T 存储元素,为了将项目恢复到原始的未排序顺序:
static unsafe void RevertSortIndex<T>(T[] rg, int[] keys)
{
int i, k, c;
int* rev = stackalloc int[c = rg.Length];
for (i = 0; i < c; i++)
rev[k = keys[i]] = k != i ? i : -1;
do
if ((i = rev[--c]) != c && i >= 0)
{
T t = rg[k = c];
do
{
rg[k] = rg[i];
rev[k] = -1;
}
while ((i = rev[k = i]) != c);
rg[k] = t;
rev[k] = -1;
}
while (c > 0);
}
为了只使用单个T 元素进行交换,并且每个元素仅移动一次到其最终位置,您必须按照数据确定的非常特定的顺序进行交换。临时反向索引 (rev) 简化了这一点,该索引很容易从 keys 创建。这里显示为stackalloc,但如果您不想走这条路,您可以轻松地将其替换为托管的int[] 分配。
无需过多详细介绍,任何排序索引都包含一个或多个从一个链接到另一个的项目“链”,并且遵循每个链为您提供了一个最佳顺序,您可以将这些元素恢复到其原始位置,同时只保留一个临时的T。这就是内部 do...while 循环的作用。
需要外部while...循环来扫描额外的链,因为排序索引作为一个整体可能有多个独立的链,它们都需要遵循。重要的是,为了得到正确的结果,每条链必须只处理一次,不能再处理。因此,为了查明任何给定的交换是否已经被处理,它在rev 临时反向索引中的条目被设置为-1。这表明rg 中对应的T 元素已被移动(作为先前链的一部分)。
这是完整的用法示例:
var rgs = new[] { "xyz", "a", "", "bb", "pdq" };
int[] idx = SortAndIndex(rgs);
// rgs: { "", "a", "bb", "pdz", "xyz" }
// idx: { 2, 1, 3, 4, 0 }
RevertSortIndex(rgs, idx);
// rgs: { "xyz", "a", "", "bb", "pdq" }
// idx: { 2, 1, 3, 4, 0 } (unchanged)
最后一点是SortAndIndex 与RevertSortIndex 的组合可能会给出rgs 未修改的外观,但这不应依赖于并发目的。如果rgs 同时从其他地方可见,则中间状态将可见。