简体   繁体   English

使用一维索引访问多维数组

[英]Accessing multi-dimensional array with one-dimensional index

See the piece of code below. 请参见下面的代码。 What is the way to access a multi-dimensional array with a one-dimensional index. 用一维索引访问多维数组的方式是什么。 Foreach can do it. Foreach可以做到。 Yeah I know, IEnumerable with yield isn't the same as an index. 是的,我知道,带有收益的IEnumerable与索引不同。 Should I use a foreach and create a new array? 我应该使用foreach并创建一个新数组吗? Or can I do it without creating a new array? 还是可以在不创建新数组的情况下做到这一点?

    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

If by one-dimensional index you mean an index that wraps around the multidimensional array's rows in order from top to bottom from left to right, then you can calculate x and y from it using this formula: 如果用一维索引表示一个索引,该索引按从上到下从左到右的顺序环绕多维数组的行,则可以使用以下公式从中计算xy

x = index % width
y = index / width

If you want not just to read the values but also set them in the same order as foreach traverses the array, you can use the following general indexer class: 如果您不仅要读取值,还希望以与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;
        }
    }
}

And you can use it like this for setting the whole array: 您可以像这样使用它来设置整个数组:

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

How about aproaching it from the other end? 从另一端提出要求怎么样?

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

As for the enumerator, it's really simple - it only allows sequential access. 对于枚举器,它非常简单-仅允许顺序访问。 Internally, it holds an array of the indices and increments them one by one. 在内部,它保存一个索引数组,并将它们一个接一个地递增。 It doesn't use any hack to make the array one-dimensional or anything - that would be an ugly violation of the framework guarantees. 它不使用任何技巧使数组成为一维或其他任何东西-这将严重违反框架保证。 And there's no yield either, this was written long before yield was implemented :) 而且也没有收益,这是在实现收益之前就写的:)

You could write such a hack yourself, of course, using unsafe code. 当然,您可以使用unsafe代码自己编写此类hack。 However, it's a really bad idea. 但是,这是一个非常糟糕的主意。 You don't really know how such arrays are organized in memory - that's an implementation detail of the runtime. 真的不知道怎么阵列等存储器被组织-这是运行时的实现细节。 Just don't. 只是不要。

Instead, figure out a reasonable addressing schema. 而是,找出一个合理的寻址方案。 If it makes sense to have a single index, why is the array multi-dimensional in the first place? 如果只有一个索引有意义,那么为什么数组首先是多维的呢? If it makes sense to have the array multi-dimensional, why are you accessing it in a single-dimensional manner? 如果使数组具有多维性是有意义的,为什么要以单维方式访问它呢?

If it does make sense, for some reason, you probably want to make your own wrapper class around some internal array, rather than using just arrays. 如果确实有道理,出于某种原因,您可能想围绕某个内部数组创建自己的包装器类,而不是仅使用数组。 It's as simple as 就这么简单

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

Which you can then use like 然后您可以使用像

var ar = new DualAccessArray<string>(10, 10);
ar[15] = "Hi!";
Console.WriteLine(ar[1, 5]);

You can fit this to your needs. 您可以满足您的需求。 For example, it might make more sense to have a different kind of addressing and memory layout, it might make sense to have the array single-dimensional, you might want to wrap an existing array... it all depends on what you're actually trying to do. 例如,使用不同类型的寻址和内存布局可能更有意义,将数组设为一维可能更有意义,您可能希望包装一个现有数组……这一切都取决于您要使用的内容实际尝试做。

Foreach can do it Foreach可以做到

No it can't. 不,它不能。 There's really no analogy. 真的没有比喻。

Since a multi-dimensional array is ipso facto an array with more than one dimension, you've got three options. 由于多维数组实际上是一个具有多个维的数组,因此您有三个选择。

  1. Stop using a multi-dimensional array. 停止使用多维数组。 If this sort of operation predominates your use then an array of arrays (a "jagged array") may be a better match to what you want. 如果这种操作占主导地位,则数组数组(“锯齿状数组”)可能会更符合您的需求。

  2. Ignore the details of the array and use pointers into memory. 忽略数组的详细信息,并使用指向内存的指针。 Best avoided if not vitally necessary. 如果不是非常必要,最好避免。

  3. Just use both indices. 只需使用两个索引。 You can find the range of the other index easily enough, so do that. 您可以很容易地找到其他索引的范围,因此可以做到。

The following will operate on a two-dimensional array, and when given a value for the first index iterate through all the values of that index. 下面的操作将在二维数组上进行,并且在给定第一个索引的值时,将迭代该索引的所有值。

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

Answering the title here. 在这里回答标题。 Let's say we need to access an arbitrary-dimensional array with one-dimensional index. 假设我们需要使用一维索引访问任意维数组。

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

The output is 0 1 2 3 4... 53 . 输出为0 1 2 3 4... 53

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

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