【问题标题】:How to sort a part of an array with int64 indicies in C#?如何在 C# 中使用 int64 索引对数组的一部分进行排序?
【发布时间】:2010-10-25 10:16:35
【问题描述】:

.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 中看到的,我使用的是整数而不是长整数。目前这对我来说已经足够了,尽管我有最初的问题(应用程序在不断变化,以防你无法分辨)。但是,如果需要,该类也应该能够处理 long。

【问题讨论】:

  • 你的意思是你正在排序超过 2^32 个元素?
  • @Jared:不,他希望能够对具有大索引的数组进行排序,但不能对整个数组进行排序——只是其中的一个子集。但是,BCL 没有办法做到这一点。

标签: c# .net arrays sorting int64


【解决方案1】:

如果您对超过 2^32 个元素进行排序,那么最好还是编写自己的、更高效的排序算法。

【讨论】:

  • 他(可能)有很多元素,但只想对子集进行排序,而不是对整个数组进行排序。 Array.Sort 适用于整个数组。
  • @Annath:如果某些东西对 2^32 个元素(快速排序)有效,那么它可能对超过 2^32 个元素也很有效。我怀疑我能比快速排序做得更好。 @Reed:我想我将不得不对不道德的大小进行分支。如果它们足够小,请使用允许我指定边界的排序重载。如果它们不够小,则对整个数组进行排序并处理排序超过我需要排序的性能损失。
【解决方案2】:

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

【讨论】:

  • 我很惊讶他们有 array.Get(long) 但没有 array.Sort(long,long)。
  • 如果您可以为副本腾出内存,这是一个合理的解决方案。就我而言,我不能。
  • 另外,您的建议不必局限于排序少于 2^32 个元素。
【解决方案3】:

你会发现,即使在 64 位框架上,一个数组的最大元素数也是int.MaxValue

接受或返回Int64 的现有方法只是在内部将long 值强制转换为Int32,并且在参数的情况下,如果long 参数不在@ 之间,则会抛出ArgumentOutOfRangeException 987654328@和int.MaxValue

例如LongLength属性,它返回一个Int64,只是转换并返回Length属性的值:

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

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

【讨论】:

  • 嗯。我原本没想到。我认为 LongLength 的存在意味着可能存在大于 Int32 的数组。您能否提供您的主张的参考资料?
  • @fantius,我还没有找到任何确认这种行为的参考资料,所以我怀疑这是一个可能会在某个时候改变的实现细节。您可以通过使用 Reflector 检查 Array 类来自己检查:red-gate.com/products/reflector
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-08
  • 2011-08-03
相关资源
最近更新 更多