簡體   English   中英

遍歷任意維度的數組

[英]Iterate through an array of arbitrary dimension

使用c#,如何遍歷未知維度的多維數組?

例如,考慮將數組的每個元素設置為指定值,需要迭代數組的所有條目。 該方法應處理以下所有情況,並使用值4填充所有條目,而不管傳遞的數組的大小。

ClearArray(new int[3], 4);
ClearArray(new int[3,3], 4);
ClearArray(new int[3, 3, 3, 3], 4);

方法簽名顯然看起來像

static void ClearArray(Array a, int val) { ... }

我知道如何迭代一個維度:

for (int i=0; i<a.GetLength(dimension); i++) 
{
    ...
}

注意 :這個問題不是關於2D數組,3D數組,也不是4D數組。 它應該處理Array對象上的Rank屬性所說的任何維度。

使用詞典順序
索引是“數字”的序列。 在每次迭代時,最后“數字”在其界限內遞增,然后下一個“數字”遞增,等等

Func<Array, int[]> firstIndex = 
  array => Enumerable.Range(0, array.Rank)
         .Select(_i => array.GetLowerBound(_i))
         .ToArray();

Func<Array, int[], int[]> nextIndex = (array, index) => 
  {
    for (int i = index.Length-1; i >= 0; --i)
    {
       index[i]++;
       if (index[i] <= array.GetUpperBound(i))
         return index;
       index[i] = array.GetLowerBound(i);
    }
    return null;
  };

for (var index = firstIndex(array); index != null; index = nextIndex(array, index))
{
   var v = array.GetValue(index);
   ...
   array.SetValue(newValue, index);
}

您可以從一堆零件中構建解決方案。 解決方案的草圖是:制作從0到長度為1的一系列序列,為陣列的每個維度創建一個序列。 然后取這些序列的笛卡爾積。 這給你一系列索引。

讓我們從產品開始:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
    this IEnumerable<IEnumerable<T>> sequences) 
{ 
  IEnumerable<IEnumerable<T>> emptyProduct = 
    new[] { Enumerable.Empty<T>() }; 
  return sequences.Aggregate( 
    emptyProduct, 
    (accumulator, sequence) => 
      from accseq in accumulator 
      from item in sequence 
      select accseq.Concat(new[] {item})); 
}

我在這里討論這段代碼的工作原理:

http://blogs.msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx

我們需要一系列序列。 什么序列?

var sequences = from dimension in Enumerable.Range(0, array.Rank)
        select Enumerable.Range(array.GetLowerBound(dimension), array.GetLength(dimension));

所以我們有序列,比如:

{
   { 0, 1, 2 },
   { 0, 1, 2, 3 }
}

現在計算產品:

var product = sequences.CartesianProduct();

所以產品是

{
   { 0, 0 },
   { 0, 1 },
   { 0, 2 },
   { 0, 3 },
   { 1, 0 },
   { 1, 1 },
   { 1, 2 },
   { 1, 3 },
   { 2, 0 },
   { 2, 1 },
   { 2, 2 },
   { 2, 3 }
}

現在你可以說

foreach(IEnumerable<int> indices in product)
    array.SetValue(value, indices.ToArray());

這一切都有意義嗎?

最快的解決方案是Buffer.BlockCopy:

static void ClearArray(Array array, int val)
{
  var helper = Enumerable.Repeat(val, Math.Min(array.Length, 1024)).ToArray();
  var itemSize = 4;


  Buffer.BlockCopy(helper, 0, array, 0, helper.Length * itemSize);
  for (var len = helper.Length; len < array.Length; len *= 2)
  {
    Buffer.BlockCopy(array, 0, array, len * itemSize, Math.Min(len, array.Length - len) * itemSize);
  }
}

static int Count(Array array, Func<int, bool> predicate)
{
  var helper = new int[Math.Min(array.Length, 4096)];
  var itemSize = 4;

  var count = 0;
  for (var offset = 0; offset < array.Length; offset += helper.Length)
  {
    var len = Math.Min(helper.Length, array.Length - offset);
    Buffer.BlockCopy(array, offset * itemSize, helper, 0, len * itemSize);
    for (var i = 0; i < len; ++i)
      if (predicate(helper[i]))
        count++;
  } 
  return count;
}

統計:

time: 00:00:00.0449501, method: Buffer.BlockCopy
time: 00:00:01.4371424, method: Lexicographical order
time: 00:00:01.3588629, method: Recursed
time: 00:00:06.2005057, method: Cartesian product with index array reusing
time: 00:00:08.2433531, method: Cartesian product w/o index array reusing

統計(計數功能):

time: 00:00:00.0812866, method: Buffer.BlockCopy
time: 00:00:02.7617093, method: Lexicographical order

碼:

  Array array = Array.CreateInstance(typeof(int), new[] { 100, 200, 400 }, new[] { -10, -20, 167 });
  foreach (var info in new [] 
    { 
      new {Name = "Buffer.BlockCopy", Method = (Action<Array, int>)ClearArray_BufferCopy},
      new {Name = "Lexicographical order", Method = (Action<Array, int>)ClearArray_LexicographicalOrder}, 
      new {Name = "Recursed", Method = (Action<Array, int>)ClearArray_Recursed}, 
      new {Name = "Cartesian product with index array reusing", Method = (Action<Array, int>)ClearArray_Cartesian_ReuseArray}, 
      new {Name = "Cartesian product w/o index array reusing", Method = (Action<Array, int>)ClearArray_Cartesian}, 
    }
   )
  {
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    var count = 10;
    for (var i = 0; i < count; ++i)
      info.Method(array, i);
    stopwatch.Stop();
    Console.WriteLine("time: {0}, method: {1}", TimeSpan.FromTicks(stopwatch.Elapsed.Ticks / count), info.Name);
  }

一個簡單的兩步解決方案,沒有嘗試優化:

    public static void ClearArray(Array a, int val)
    {
        int[] indices = new int[a.Rank];
        ClearArray(a, 0, indices, val);
    }

    private static void ClearArray(Array a, int r, int[] indices, int v)
    {
        for (int i = 0; i < a.GetLength(r); i++)
        {
            indices[r] = i;

            if (r + 1 < a.Rank)
                ClearArray(a, r + 1, indices, v);
            else
                a.SetValue(v, indices);
        }
    }

使用Array.Rank確定維數,然后使用Array.GetLowerBound(int dimension)和Array.GetUpperBound(int dimension)來了解每個給定排名的范圍。

它沒有指定你的迭代器應該如何工作(例如,迭代的順序是否有任何語義)。 但是,要實現ClearArray(),迭代的順序無關緊要。

使用a.SetValue(object value,params int [] indices)設置為ClearArray方法指定的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM