簡體   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