繁体   English   中英

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

[英]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

如果用一维索引表示一个索引,该索引按从上到下从左到右的顺序环绕多维数组的行,则可以使用以下公式从中计算xy

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可以做到

不,它不能。 真的没有比喻。

由于多维数组实际上是一个具有多个维的数组,因此您有三个选择。

  1. 停止使用多维数组。 如果这种操作占主导地位,则数组数组(“锯齿状数组”)可能会更符合您的需求。

  2. 忽略数组的详细信息,并使用指向内存的指针。 如果不是非常必要,最好避免。

  3. 只需使用两个索引。 您可以很容易地找到其他索引的范围,因此可以做到。

下面的操作将在二维数组上进行,并且在给定第一个索引的值时,将迭代该索引的所有值。

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.

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