【发布时间】:2011-01-11 15:57:53
【问题描述】:
我正在寻找一种将多维数组中的每个值设置为单个值的方法。问题是在编译时维数是未知的——它可能是一维的,也可能是四维的。由于foreach 不允许您设置值,我可以实现此目标的一种方法是什么?非常感谢。
【问题讨论】:
-
锯齿状数组还是多维数组?
我正在寻找一种将多维数组中的每个值设置为单个值的方法。问题是在编译时维数是未知的——它可能是一维的,也可能是四维的。由于foreach 不允许您设置值,我可以实现此目标的一种方法是什么?非常感谢。
【问题讨论】:
虽然这个问题表面上看起来很简单,但实际上比看起来要复杂。 但是,通过认识到访问多维(甚至锯齿状)数组中的每个位置是一个笛卡尔对数组索引集的乘积操作 - 我们可以简化解决方案......并最终编写一个更优雅的解决方案。
我们将利用 Eric Lippert's LINQ Cartesian Product 实现来完成繁重的工作。如果您愿意,可以在他的博客上阅读有关其工作原理的更多信息。
虽然此实现特定于访问多维数组的单元格 - 应该相对容易看出如何扩展它以访问锯齿状数组。
public static class EnumerableExt
{
// Eric Lippert's Cartesian Product operator...
public 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 }));
}
}
class MDFill
{
public static void Main()
{
// create an arbitrary multidimensional array
Array mdArray = new int[2,3,4,5];
// create a sequences of sequences representing all of the possible
// index positions of each dimension within the MD-array
var dimensionBounds =
Enumerable.Range(0, mdArray.Rank)
.Select(x => Enumerable.Range(mdArray.GetLowerBound(x),
mdArray.GetUpperBound(x) - mdArray.GetLowerBound(x)+1));
// use the cartesian product to visit every permutation of indexes
// in the MD array and set each position to a specific value...
int someValue = 100;
foreach( var indexSet in dimensionBounds.CartesianProduct() )
{
mdArray.SetValue( someValue, indexSet.ToArray() );
}
}
}
现在可以轻松将此代码分解为可重用的方法,该方法可用于锯齿状或多维数组...或任何可被视为矩形数组的数据结构。
【讨论】:
Array.Length 会告诉你数组被声明要存储的元素数量,因此可以按如下方式遍历数组数组(无论是矩形还是锯齿状):
for(var i=0; i<myMDArray.Length; i++)
for(var j=0; j < myMDArray[i].Length; i++)
DoSomethingTo(myMDArray[i][j]);
如果数组是矩形的(所有子数组的长度相同),可以获取第一个数组的Length并存入一个变量;它会稍微提高性能。
当维数未知时,可以递归:
public void SetValueOn(Array theArray, Object theValue)
{
if(theArray[0] is Array) //we haven't hit bottom yet
for(int a=0;a<theArray.Length;a++)
SetValueOn(theArray[a], theValue);
else if(theValue.GetType().IsAssignableFrom(theArray[0].GetType()))
for(int i=0;i<theArray.Length;i++)
theArray[i] = theValue;
else throw new ArgumentException(
"theValue is not assignable to elements of theArray");
}
【讨论】:
myArray[][] 解决方案
我认为没有直接的方法可以做到这一点,因此您需要使用Rank 属性来获取维度的数量和SetValue 方法(该方法将每个维度的索引数组作为参数)。
一些帮助您入门的代码 sn-p(用于标准多维数组):
bool IncrementLastIndex(Array ar, int[] indices) {
// Return 'false' if indices[i] == ar.GetLength(i) for all 'i'
// otherwise, find the last index such that it can be incremented,
// increment it and set all larger indices to 0
for(int dim = indices.Length - 1; dim >= 0; dim--) {
if (indices[dim] < ar.GetLength(dim)) {
indices[dim]++;
for(int i = dim + 1; i < indices.Length; i++) indices[i] = 0;
return;
}
}
}
void ClearArray(Array ar, object val) {
var indices = new int[ar.Rank];
do {
// Set the value in the array to specified value
ar.SetValue(val, indices);
} while(IncrementLastIndex(ar, indices));
}
【讨论】:
myArray[,] 解决方案
Array.Clear 方法可以让您清除(设置为默认值)多维数组(即int[,])中的所有元素。所以如果只想清空数组,可以写Array.Clear(myArray, 0, myArray.Length);
似乎没有任何方法可以将数组的所有元素设置为任意值。
请注意,如果您将其用于锯齿状数组(即int[][]),您最终会得到一个数组的空引用数组。也就是说,如果你写:
int[][] myArray;
// do some stuff to initialize the array
// now clear the array
Array.Clear(myArray, 0, myArray.Length);
那么myArray[0] 将是null。
【讨论】:
您是说要遍历数组的每个元素和(如果可用)每个维度并设置每个值吗?
如果是这种情况,您将创建一个迭代维度并设置值的递归函数。查看 MSDN 上的Array.Rank 属性和 MSDN 上的Array.GetUpperBound 函数。
最后,我确信通用 List<T> 有某种方法可以做到这一点。
【讨论】:
List<T> 似乎没有这样的东西。 List.Clear() 会将每个项目设置为其默认值,但不会设置为任意值。