[英]Accessing multi-dimensional array with one-dimensional index
请参见下面的代码。 用一维索引访问多维数组的方式是什么。 Foreach可以做到。 是的,我知道,带有收益的IEnumerable与索引不同。 我应该使用foreach并创建一个新数组吗? 还是可以在不创建新数组的情况下做到这一点?
int[,] myArray = new int[2, 2];
myArray[0,0] = 1;
myArray[1,1] = 2;
myArray[0,0] = 3;
myArray[1,1] = 4;
foreach (var value in myArray)
{
Console.Write(value);
}
var valueAtIndex = myArray[2]; //Won't compile, error: Wrong number of indices inside []; expected 2
如果用一维索引表示一个索引,该索引按从上到下从左到右的顺序环绕多维数组的行,则可以使用以下公式从中计算x
和y
:
x = index % width
y = index / width
如果您不仅要读取值,还希望以与foreach
遍历数组相同的顺序设置它们,则可以使用以下常规索引器类:
public class ArrayIndexer
{
readonly int totalLength;
readonly int lastIndexLength;
readonly int[] lengths;
readonly int[] lowerBounds;
int current;
readonly int[] currentZeroBased;
public ArrayIndexer(int[] lengths, int[] lowerBounds)
{
lastIndexLength = lengths[lengths.Length - 1];
totalLength = lengths[0];
for (int i = 1; i < lengths.Length; i++)
{
totalLength *= lengths[i];
}
this.lengths = lengths;
this.lowerBounds = lowerBounds;
currentZeroBased = new int[lengths.Length];
current = -1;
}
public bool MoveNext()
{
current++;
if (current != 0)
{
int currLastIndex = current % lastIndexLength;
currentZeroBased[currentZeroBased.Length - 1] = currLastIndex;
if (currLastIndex == 0)
{
for (int i = currentZeroBased.Length - 2; i >= 0; i--)
{
currentZeroBased[i]++;
if (currentZeroBased[i] != lengths[i])
break;
currentZeroBased[i] = 0;
}
}
}
return current < totalLength;
}
public int[] Current
{
get
{
int[] result = new int[currentZeroBased.Length];
for (int i = 0; i < result.Length; i++)
{
result[i] = currentZeroBased[i] + lowerBounds[i];
}
return result;
}
}
}
您可以像这样使用它来设置整个数组:
int[,] myArray = new int[2, 2];
ArrayIndexer arrayIndexer = new ArrayIndexer(new[] {2, 2}, new[] {0, 0});
int i = 0;
while (arrayIndexer.MoveNext())
{
myArray.SetValue(++i, arrayIndexer.Current);
}
从另一端提出要求怎么样?
int rows=2, cols=2;
int[] myArray = new int[rows*cols];
public void setMyArray(int[] myArray, int x, int y, int value)
{
myArray[(y)*cols + x] = value;
}
setMyArray(0,0,1);
setMyArray(0,1,2);
setMyArray(1,0,3);
setMyArray(1,1,4);
对于枚举器,它非常简单-仅允许顺序访问。 在内部,它保存一个索引数组,并将它们一个接一个地递增。 它不使用任何技巧使数组成为一维或其他任何东西-这将严重违反框架保证。 而且也没有收益,这是在实现收益之前就写的:)
当然,您可以使用unsafe
代码自己编写此类hack。 但是,这是一个非常糟糕的主意。 你真的不知道怎么阵列等存储器被组织-这是运行时的实现细节。 只是不要。
而是,找出一个合理的寻址方案。 如果只有一个索引有意义,那么为什么数组首先是多维的呢? 如果使数组具有多维性是有意义的,为什么要以单维方式访问它呢?
如果确实有道理,出于某种原因,您可能想围绕某个内部数组创建自己的包装器类,而不是仅使用数组。 就这么简单
public class DualAccessArray<T>
{
private readonly T[,] _internalArray;
private readonly int _width;
private readonly int _height;
public DualAccessArray(int width, int height)
{
_width = width;
_height = height;
_internalArray = new T[width, height];
}
public T this[long index]
{
get { return _internalArray[index / _width, index % _width]; }
set { _internalArray[index / _width, index % _width] = value; }
}
public T this[int x, int y]
{
get { return _internalArray[x, y]; }
set { _internalArray[x, y] = value; }
}
}
然后您可以使用像
var ar = new DualAccessArray<string>(10, 10);
ar[15] = "Hi!";
Console.WriteLine(ar[1, 5]);
您可以满足您的需求。 例如,使用不同类型的寻址和内存布局可能更有意义,将数组设为一维可能更有意义,您可能希望包装一个现有数组……这一切都取决于您要使用的内容实际尝试做。
Foreach可以做到
不,它不能。 真的没有比喻。
由于多维数组实际上是一个具有多个维的数组,因此您有三个选择。
停止使用多维数组。 如果这种操作占主导地位,则数组数组(“锯齿状数组”)可能会更符合您的需求。
忽略数组的详细信息,并使用指向内存的指针。 如果不是非常必要,最好避免。
只需使用两个索引。 您可以很容易地找到其他索引的范围,因此可以做到。
下面的操作将在二维数组上进行,并且在给定第一个索引的值时,将迭代该索引的所有值。
public static IEnumerable<T> GetRow<T>(this T[,] array, int index)
{
int last = array.GetUpperBound(1);
for(int idx = array.GetLowerBound(1); idx <= last; ++idx)
yield return array[index, idx];
}
在这里回答标题。 假设我们需要使用一维索引访问任意维数组。
var test = new [,,,]
{
{
{
{ 00, 01, 02 }, { 03, 04, 05 }, { 06, 07, 08 }
},
{
{ 09, 10, 11 }, { 12, 13, 14 }, { 15, 16, 17 }
},
{
{ 18, 19, 20 }, { 21, 22, 23 }, { 24, 25, 26 }
}
},
{
{
{ 27, 28, 29 }, { 30, 31, 32 }, { 33, 34, 35 }
},
{
{ 36, 37, 38 }, { 39, 40, 41 }, { 42, 43, 44 }
},
{
{ 45, 46, 47 }, { 48, 49, 50 }, { 51, 52, 53 }
}
}
};
Func<int, IEnumerable<int>, IEnumerable<int>> unwrapLinearIndex = (linidx, bounds) =>
bounds.Select((b, i) => bounds.Take(i).Aggregate(linidx, (acc, bnd) => acc / bnd) % b);
// Reverse() to enumerate innermost dimensions first
var testBounds = new int[test.Rank].Select((_, d) => test.GetUpperBound(d) + 1).Reverse().ToArray();
for (int i = 00; i < test.Length; i++)
{
var indexes = unwrapLinearIndex(i, testBounds).Reverse().ToArray();
Console.Write($"{test.GetValue(indexes)} ");
}
输出为0 1 2 3 4... 53
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.