使用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阵列无关。它应该处理Rank
对象上的Array
属性所说的任何维度。
答案 0 :(得分:7)
使用Lexicographical order:
索引是“数字”的序列。在每次迭代时,最后一个“数字”在其界限内递增,然后下一个“数字”递增,等等
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);
}
答案 1 :(得分:6)
您可以从一堆零件中构建解决方案。解决方案的草图是:制作从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());
这一切都有意义吗?
答案 2 :(得分:5)
最快的解决方案是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);
}
答案 3 :(得分:3)
一个简单的两步解决方案,未尝试进行优化:
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);
}
}
答案 4 :(得分:0)
使用Array.Rank确定维数,然后使用Array.GetLowerBound(int dimension)和Array.GetUpperBound(int dimension)来了解每个给定排名的范围。
没有指定你的迭代器应该如何工作(例如,迭代的顺序是否有任何语义)。但是,要实现ClearArray(),迭代的顺序无关紧要。
使用a.SetValue(object value,params int [] indices)设置为ClearArray方法指定的值。