简体   繁体   English

遍历任意维度的数组

[英]Iterate through an array of arbitrary dimension

Using c#, how to iterate through an multidimensional array of unknown dimensions? 使用c#,如何遍历未知维度的多维数组?

For example consider setting each element of the array to a specified value, all entries of the array needs to be iterated. 例如,考虑将数组的每个元素设置为指定值,需要迭代数组的所有条目。 The method should handle all the following cases and fill all entries with the value 4, regardless of the dimension of the array passed. 该方法应处理以下所有情况,并使用值4填充所有条目,而不管传递的数组的大小。

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

The methods signature obviously looks something like 方法签名显然看起来像

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

I know how to iterate through one dimension: 我知道如何迭代一个维度:

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

Note : This question is not about 2D arrays, 3D arrays, nor 4D arrays. 注意 :这个问题不是关于2D数组,3D数组,也不是4D数组。 It should handle whatever dimension the Rank property on the Array object says. 它应该处理Array对象上的Rank属性所说的任何维度。

Use Lexicographical order : 使用词典顺序
Index is sequence of "digits". 索引是“数字”的序列。 On each iteration last "digit" incremented while it in bounds, then next "digit" incremented, etc 在每次迭代时,最后“数字”在其界限内递增,然后下一个“数字”递增,等等

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);
}

You can build a solution out of a bunch of parts. 您可以从一堆零件中构建解决方案。 The sketch of the solution is: make a bunch of sequences from zero to length-1, one sequence for each dimension of the array. 解决方案的草图是:制作从0到长度为1的一系列序列,为阵列的每个维度创建一个序列。 Then take the Cartesian product of those sequences. 然后取这些序列的笛卡尔积。 That gives you a sequence of indexes. 这给你一系列索引。

Let's start with the product: 让我们从产品开始:

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})); 
}

I discuss how this code works here: 我在这里讨论这段代码的工作原理:

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

We need a sequence of sequences. 我们需要一系列序列。 What sequences? 什么序列?

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

So we have sequences as, say: 所以我们有序列,比如:

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

Now compute the product: 现在计算产品:

var product = sequences.CartesianProduct();

So the product is 所以产品是

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

And now you can say 现在你可以说

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

Does that all make sense? 这一切都有意义吗?

Fastest solution is Buffer.BlockCopy: 最快的解决方案是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;
}

Statistic: 统计:

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

Statistic (Count function): 统计(计数功能):

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

Code: 码:

  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);
  }

A simple 2-step solution, no optimization attempted: 一个简单的两步解决方案,没有尝试优化:

    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);
        }
    }

Use Array.Rank to determine the number of dimensions, then Array.GetLowerBound(int dimension) and Array.GetUpperBound(int dimension) to understand the range for each given rank. 使用Array.Rank确定维数,然后使用Array.GetLowerBound(int dimension)和Array.GetUpperBound(int dimension)来了解每个给定排名的范围。

It's not specified how your iterator should work (eg are there any semantic to the order of iteration). 它没有指定你的迭代器应该如何工作(例如,迭代的顺序是否有任何语义)。 However, to implement ClearArray() the order of iteration should not matter. 但是,要实现ClearArray(),迭代的顺序无关紧要。

Use a.SetValue(object value, params int[] indices) to set the value specified for your ClearArray method. 使用a.SetValue(object value,params int [] indices)设置为ClearArray方法指定的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM