如何在C#中对具有int64索引的数组的一部分进行排序?

4
.Net框架有一个Array.Sort重载,允许指定排序操作的起始和结束索引。但是这些参数仅为32位。因此,当描述排序范围的索引只能使用64位数字指定时,我不知道如何对大数组的一部分进行排序。我想我可以复制并修改框架的排序实现,但那并不理想。
更新:
我创建了两个类来帮助我解决这些和其他大型数组问题。其中之一是,在达到内存限制之前很久,我开始收到OutOfMemoryException异常。我假设这是因为请求的内存可能可用但不连续。所以为此,我创建了BigArray类,它是一个通用的、动态可调整大小的数组列表。它比框架的通用列表类具有更小的内存占用,并且不需要整个数组是连续的。我还没有测试性能损失,但我确定会有。
  public class BigArray<T> : IEnumerable<T>
  {
    private long capacity;
    private int itemsPerBlock;
    private int shift;
    private List<T[]> blocks = new List<T[]>();

    public BigArray(int itemsPerBlock)
    {
      shift = (int)Math.Ceiling(Math.Log(itemsPerBlock) / Math.Log(2));
      this.itemsPerBlock = 1 << shift;
    }

    public long Capacity
    {
      get
      {
        return capacity;
      }
      set
      {
        var requiredBlockCount = (value - 1) / itemsPerBlock + 1;
        while (blocks.Count > requiredBlockCount)
        {
          blocks.RemoveAt(blocks.Count - 1);
        }
        while (blocks.Count < requiredBlockCount)
        {
          blocks.Add(new T[itemsPerBlock]);
        }
        capacity = (long)itemsPerBlock * blocks.Count;
      }
    }

    public T this[long index]
    {
      get
      {
        Debug.Assert(index < capacity);
        var blockNumber = (int)(index >> shift);
        var itemNumber = index & (itemsPerBlock - 1);
        return blocks[blockNumber][itemNumber];
      }
      set
      {
        Debug.Assert(index < capacity);
        var blockNumber = (int)(index >> shift);
        var itemNumber = index & (itemsPerBlock - 1);
        blocks[blockNumber][itemNumber] = value;
      }
    }

    public IEnumerator<T> GetEnumerator()
    {
      for (long i = 0; i < capacity; i++)
      {
        yield return this[i];
      }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }

  }

回到排序的问题上来...我真正需要的是一种按顺序对数组的每个元素进行操作的方法。但是由于这些数组非常大,复制数据、排序、执行操作,然后丢弃已排序的副本(必须保持原始顺序)是不可行的。因此,我创建了一个静态类OrderedOperation,它允许您以排序的方式对未排序的数组的每个元素执行任意操作,并且具有较低的内存占用(在这里将内存换取执行时间)。

  public static class OrderedOperation
  {
    public delegate void WorkerDelegate(int index, float progress);

    public static void Process(WorkerDelegate worker, IEnumerable<int> items, int count, int maxItem, int maxChunkSize)
    {
      // create a histogram such that a single bin is never bigger than a chunk
      int binCount = 1000;
      int[] bins;
      double binScale;
      bool ok;
      do
      {
        ok = true;
        bins = new int[binCount];
        binScale = (double)(binCount - 1) / maxItem;
        int i = 0;
        foreach (int item in items)
        {
          bins[(int)(binScale * item)]++;
          if (++i == count)
          {
            break;
          }
        }
        for (int b = 0; b < binCount; b++)
        {
          if (bins[b] > maxChunkSize)
          {
            ok = false;
            binCount *= 2;
            break;
          }
        }
      } while (!ok);

      var chunkData = new int[maxChunkSize];
      var chunkIndex = new int[maxChunkSize];
      var done = new System.Collections.BitArray(count);
      var processed = 0;
      var binsCompleted = 0;
      while (binsCompleted < binCount)
      {
        var chunkMax = 0;
        var sum = 0;
        do
        {
          sum += bins[binsCompleted];
          binsCompleted++;
        } while (binsCompleted < binCount - 1 && sum + bins[binsCompleted] <= maxChunkSize);
        Debug.Assert(sum <= maxChunkSize);
        chunkMax = (int)Math.Ceiling((double)binsCompleted / binScale);
        var chunkCount = 0;
        int i = 0;
        foreach (int item in items)
        {
          if (item < chunkMax && !done[i])
          {
            chunkData[chunkCount] = item;
            chunkIndex[chunkCount] = i;
            chunkCount++;
            done[i] = true;
          }
          if (++i == count)
          {
            break;
          }
        }
        Debug.Assert(sum == chunkCount);
        Array.Sort(chunkData, chunkIndex, 0, chunkCount);
        for (i = 0; i < chunkCount; i++)
        {
          worker(chunkIndex[i], (float)processed / count);
          processed++;
        }
      }
      Debug.Assert(processed == count);
    }
  }

这两个类可以一起使用(这就是我使用它们的方式),但不必如此。我希望其他人也能发现它们有用。但我承认,它们是边缘情况下的类。欢迎提问。如果我的代码糟糕,我也想听听建议。
最后一个想法:正如您在OrderedOperation中所看到的,我正在使用int而不是long。尽管我曾经有过原始问题(如果您无法告诉,该应用程序正在不断变化),但目前对我来说已足够。但是,该类也应该能够处理长整型,如果需要的话。

2
你的意思是你要对超过2^32个元素进行排序? - Jared Updike
1
@Jared:不,他想要能够对具有大索引的数组进行排序,但不是整个数组 - 只是其中的一个子集。尽管如此,在BCL中没有办法做到这一点。 - Reed Copsey
3个回答

5

即使在64位框架上,数组中元素的最大数量也是int.MaxValue

现有的接受或返回Int64的方法只是将long值内部转换为Int32,并且对于参数,如果long参数不在int.MinValueint.MaxValue之间,则会抛出ArgumentOutOfRangeException异常。

例如,返回Int64LongLength属性只是将Length属性的值转换后返回:

public long LongLength
{
    get { return (long)this.Length; }    // Length is an Int32
}

因此,我的建议是将您的Int64索引转换为Int32,然后调用现有的Sort重载之一。


嗯,我没想到会这样。我认为LongLength的存在意味着可以存在比Int32更大的数组。你能提供一下你的说法的参考资料吗? - Fantius
@fantius,我还没有找到任何证实这种行为的参考资料,所以我怀疑这可能是一个实现细节,在某个时候可能会发生变化。你可以通过使用反射器检查Array类来自己验证一下:http://www.red-gate.com/products/reflector/ - LukeH

1

由于Array.Copy需要Int64参数,因此您可以提取需要排序的部分,对其进行排序,然后将其放回。 当然,假设您要对少于2 ^ 32个元素进行排序。


我很惊讶他们有array.Get(long),但没有array.Sort(long,long)。 - Jared Updike
如果你可以为复制留出足够的内存,那么这是一个合理的解决方案。但在我的情况下,我无法这样做。 - Fantius
此外,您的建议不必局限于对少于2^32个元素进行排序。 - Fantius

0

看起来如果你要排序超过2^32个元素,最好还是自己编写更高效的排序算法。


他可能有很多元素,但只想对子集进行排序,而不是整个数组。Array.Sort 可以用于整个数组。 - Reed Copsey
1
@Annath:如果某个算法对于2^32个元素(快速排序)是高效的,那么它很可能对于超过2^32个元素也同样高效。我怀疑我无法比快速排序更好。@Reed:我想我必须根据索引的大小进行分支。如果它们足够小,则使用允许我指定边界的Sort重载。如果它们不够小,则对整个数组进行排序,并处理超出需要排序的性能损失。 - Fantius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接