How to "flatten" or "index" 3D-array in 1D array?
Asked Answered
C

15

45

I am trying to flatten 3D array into 1D array for "chunk" system in my game. It's a 3D-block game and basically I want the chunk system to be almost identical to Minecraft's system (however, this isn't Minecraft clone by any measure). In my previous 2D-games I have accessed the flattened array with following algorithm:

Tiles[x + y * WIDTH]

However, this obviously doesn't work with 3D since it's missing the Z-axis. I have no idea how to implement this sort of algorithm in 3D-space. Width, height and depth are all constants (and width is just as large as height).

Is it just x + y*WIDTH + Z*DEPTH ? I am pretty bad with math and I am just beginning 3D-programming so I am pretty lost :|

PS. The reason for this is that I am looping and getting stuff by index from it quite a lot. I know that 1D arrays are faster than multi-dimensional arrays (for reasons I cant remember :P ). Even though this may not be necessary, I want as good performance as possible :)

Clay answered 9/9, 2011 at 21:42 Comment(3)
Am I correct in saying you want a 3D array to be fit into a 1D array?Zilla
Why don't you just use 3D array?Decastro
@DMan Yes, that's correct.Clay
F
55

The algorithm is mostly the same. If you have a 3D array Original[HEIGHT, WIDTH, DEPTH] then you could turn it into Flat[HEIGHT * WIDTH * DEPTH] by

Flat[x + WIDTH * (y + DEPTH * z)] = Original[x, y, z]

As an aside, you should prefer arrays of arrays over multi-dimensional arrays in .NET. The performance differences are significant

Fisk answered 9/9, 2011 at 21:46 Comment(4)
Could you point to some source discussing the performance differences? Also, you shouldn't base your decisions just on performance.Decastro
#598220 and #469332Hamish
@svick: Some sources can be seen in the links hatchet provided. My performance note was only an aside and not the main suggestion. Jagged arrays have nearly identical syntax (original[x][y][z]), but do take more work to initialize. However, the performance benefits can become quite noticeable (2-5x speedup) depending on the usage.Fisk
If HEIGHT corresponds to the Y dimension, it should be: Flat[x + WIDTH * (y + HEIGHT * z)] = Original[x, y, z]Electronics
D
75

Here is a solution in Java that gives you both:

  • from 3D to 1D
  • from 1D to 3D

Below is a graphical illustration of the path I chose to traverse the 3D matrix, the cells are numbered in their traversal order:

2 Examples of 3D matrices

Conversion functions:

public int to1D( int x, int y, int z ) {
    return (z * xMax * yMax) + (y * xMax) + x;
}

public int[] to3D( int idx ) {
    final int z = idx / (xMax * yMax);
    idx -= (z * xMax * yMax);
    final int y = idx / xMax;
    final int x = idx % xMax;
    return new int[]{ x, y, z };
}
Diversification answered 18/12, 2015 at 20:8 Comment(2)
It's unclear in this answer whether xMax means WIDTH or WIDTH-1Teacake
yes it is, it is infinityBoren
F
55

The algorithm is mostly the same. If you have a 3D array Original[HEIGHT, WIDTH, DEPTH] then you could turn it into Flat[HEIGHT * WIDTH * DEPTH] by

Flat[x + WIDTH * (y + DEPTH * z)] = Original[x, y, z]

As an aside, you should prefer arrays of arrays over multi-dimensional arrays in .NET. The performance differences are significant

Fisk answered 9/9, 2011 at 21:46 Comment(4)
Could you point to some source discussing the performance differences? Also, you shouldn't base your decisions just on performance.Decastro
#598220 and #469332Hamish
@svick: Some sources can be seen in the links hatchet provided. My performance note was only an aside and not the main suggestion. Jagged arrays have nearly identical syntax (original[x][y][z]), but do take more work to initialize. However, the performance benefits can become quite noticeable (2-5x speedup) depending on the usage.Fisk
If HEIGHT corresponds to the Y dimension, it should be: Flat[x + WIDTH * (y + HEIGHT * z)] = Original[x, y, z]Electronics
P
40

I think the above needs a little correction. Lets say you have a HEIGHT of 10, and a WIDTH of 90, single dimensional array will be 900. By the above logic, if you are at the last element on the array 9 + 89*89, obviously this is greater than 900. The correct algorithm is:

Flat[x + HEIGHT* (y + WIDTH* z)] = Original[x, y, z], assuming Original[HEIGHT,WIDTH,DEPTH] 

Ironically if you the HEIGHT>WIDTH you will not experience an overflow, just complete bonkers results ;)

Polymerism answered 17/5, 2013 at 13:16 Comment(2)
I can't upvote or comment on the real correct answer, but Martin has it correct, the current selected answer is wrong. Essentially: data[x][y][z] = data[x + ymaxX + zmaxX*maxY]Commissionaire
yep current answer is wrong, should be height not depth. took me too long to figure this out as its the first time ive actually used a wrong SO answer to code something >.<Latanya
M
12

x + y*WIDTH + Z*WIDTH*DEPTH. Visualize it as a rectangular solid: first you traverse along x, then each y is a "line" width steps long, and each z is a "plane" WIDTH*DEPTH steps in area.

Mckibben answered 9/9, 2011 at 21:46 Comment(0)
S
7

You're almost there. You need to multiply Z by WIDTH and DEPTH:

Tiles[x + y*WIDTH + Z*WIDTH*DEPTH] = elements[x][y][z]; // or elements[x,y,z]
Swayne answered 9/9, 2011 at 21:46 Comment(1)
Could you help me confirm what the pattern would be for 4D, or 5D etcPhilanthropic
S
6

TL;DR

The correct answer can be written various ways, but I like it best when it can be written in a way that is very easy to understand and visualize. Here is the exact answer:

(width * height * z) + (width * y) + x

TS;DR

Visualize it:

someNumberToRepresentZ + someNumberToRepresentY + someNumberToRepresentX

someNumberToRepresentZ indicates which matrix we are on (depth). To know which matrix we are on, we have to know how big each matrix is. A matrix is 2d sized as width * height, simple. The question to ask is "how many matrices are before the matrix I'm on?" The answer is z:

someNumberToRepresentZ = width * height * z

someNumberToRepresentY indicates which row we are on (height). To know which row we are on, we have to know how big each row is: Each row is 1d, sized as width. The question to ask is "how many rows are before the row I'm on?". The answer is y:

someNumberToRepresentY = width * y

someNumberToRepresentX indicates which column we are on (width). To know which column we are on we simply use x:

someNumberToRepresentX = x

Our visualization then of

someNumberToRepresentZ + someNumberToRepresentY + someNumberToRepresentX

Becomes

(width * height * z) + (width * y) + x
Sexdecillion answered 9/7, 2018 at 19:2 Comment(0)
E
5

The forward and reverse transforms of Samuel Kerrien above are almost correct. A more concise (R-based) transformation maps are included below with an example (the "a %% b" is the modulo operator representing the remainder of the division of a by b):

dx=5; dy=6; dz=7  # dimensions
x1=1; y1=2; z1=3  # 3D point example
I = dx*dy*z1+dx*y1+x1; I  # corresponding 2D index
# [1] 101
x= I %% dx; x  # inverse transform recovering the x index
# [1] 1
y = ((I - x)/dx) %% dy; y  # inverse transform recovering the y index
# [1] 2
z= (I-x -dx*y)/(dx*dy); z  # inverse transform recovering the z index
# [1] 3

Mind the division (/) and module (%%) operators.

Eckstein answered 18/2, 2020 at 18:54 Comment(0)
T
4

The correct Algorithm is:

Flat[ x * height * depth + y * depth + z ] = elements[x][y][z] 
where [WIDTH][HEIGHT][DEPTH]
Thrombo answered 13/3, 2018 at 10:14 Comment(1)
Tested almost all other answers, only this one translates nested for-loops (x < width, y < height, z < depth) to arrays indexes from 0 to width * height * depth (ordered)Sunnisunnite
A
3

To better understand description of 3D array in 1D array would be ( I guess Depth in best answer is meant Y size)

IndexArray = x + y * InSizeX + z * InSizeX * InSizeY;

IndexArray = x + InSizeX * (y + z * InSizeY);
Aliment answered 7/2, 2014 at 15:45 Comment(0)
E
1

m[x][y][z] = data[xYZ + yZ + z]

x-picture:
0-YZ
.
.
x-YZ

y-picture 

0-Z
.
.
.
y-Z


summing up, it should be : targetX*YZ + targetY*Z + targetZ  
Eupepsia answered 11/3, 2020 at 19:59 Comment(0)
C
1

A python version:

from operator import mul
from functools import reduce

def idx_1D(target, shape):
        idx = 0
        for i, digit in enumerate(target):
            coeff = reduce(mul, shape[i + 1 :]) if i < len(shape) - 1 else 1
            idx += digit * coeff
        return idx
Cleaning answered 19/11, 2023 at 12:47 Comment(0)
W
0

In case, somebody is interested to flatten an nD (2D, 3D, 4D, ...) array to 1D, I wrote the below code. For example, if the size of the array in different dimensions is stored in the sizes array:

#  pseudo code
sizes = {size_x, size_y, size_z,...};

This recursive function gives you the series of {1, size_x, size_x*size_y, size_x*size_y*size_z, ...}

// i: number of the term
public int GetCoeff(int i){
        if (i==0)
            return 1;
        return sizes[i-1]*GetCoeff(i-1);
}

So, we have to multiply nD indexes by their corresponding series term and sum them to get {ix + iy*size_x + iz*size_x*size_y, ...}:

// indexNd: {ix, iy, iz, ...}
public int GetIndex1d(int[] indexNd){
    int sum =0;
    for (var i=0; i<indexNd.Length;i++)
        sum += indexNd[i]*GetCoeff(i);
    return sum;
    
}

In this code I assumed, the nD array is contiguous in memory along firstly x, then y, z, ... . So probably you call your array-like arr[z,y,x]. But, if you call them the other way, arr[x,y,z] then z is the fastest index and we like to calculate iz + iy*size_z + ix* size_z*size_y. In this case, the below function gives us the series {1, size_z, size_z*size_y, ...}:

// Dims is dimension of array, like 3 for 3D
public int GetReverseCoeff(int i){
       if (i==0)
            return 1;
        return sizes[Dims-i]*GetReverseCoeff(i-1);
}

The coefficients are stored in the right order:

public void SetCoeffs(){
    for (int i=0;i<Dims;i++)
            coeffs[Dims-i-1] = GetReverseCoeff(i);
}

The 1D index is calculated the same as before except coeffs array is used:

// indexNd: {ix, iy, iz, ...}
public int GetIndex1d(int[] indexNd){
    int sum =0;
    for (var i=0; i<indexNd.Length;i++)
        sum += indexNd[i]*coeffs[i];
    return sum;
    
}
Widen answered 26/4, 2022 at 22:2 Comment(0)
P
0

Samuel Kerrien's answer to python :

def to1D(crds,dims):
  x,y,z=crds
  xMax,yMax,zMax=dims
  return (z * xMax * yMax) + (y * xMax) + x

def to3D(idx,dims):
    xMax,yMax,zMax=dims
    z = idx // (xMax * yMax)
    idx -= (z * xMax * yMax)
    y = idx // xMax
    x = idx % xMax
    return x, y, z 
Portcullis answered 12/6, 2022 at 8:34 Comment(0)
G
0

Here is a C# implementation of a general rank matrix. The data is stored in a 1D array, and the function int GetIndex(int,int,int...) finds the index in the 1D array that corresponds to the n-th rank tensor indexes. The reverse is called int[] GetIndexes(int)

The code also supports both column-major ordering (the default) and row-major ordering.

public enum IndexOrdering
{
    ColumnMajor,
    RowMajor,
}
public class Matrix<T>
{
    readonly T[] _data;
    readonly int[] _shape;

    public Matrix(IndexOrdering ordering, int[] shape)
    {
        Ordering = ordering;
        Rank = shape.Length;
        _shape = shape;
        Size = shape.Aggregate(1, (s, l) => s * l);
        _data = new T[Size];
    }
    public Matrix(params int[] shape)
        : this(IndexOrdering.ColumnMajor, shape) { }
    public Matrix(IndexOrdering ordering, int[] shape, T[] data)
        : this(ordering, shape)
    {
        Array.Copy(data, _data, Size);
    }

    public int Rank { get; }
    public int Size { get; }
    public IReadOnlyList<int> Shape { get => _shape; }
    internal T[] Data { get => _data; }
    public IndexOrdering Ordering { get; set; }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int GetIndex(params int[] indexes)
    {
        switch (Ordering)
        {
            case IndexOrdering.ColumnMajor:
                {
                    int index = 0;
                    for (int i = 0; i < Rank; i++)
                    {
                        index = _shape[i] * index + indexes[i];
                    }
                    return index;
                }
            case IndexOrdering.RowMajor:
                {
                    int index = 0;
                    for (int i = Rank - 1; i >= 0; i--)
                    {
                        index = _shape[i] * index + indexes[i];
                    }
                    return index;
                }
            default:
                throw new NotSupportedException();
        }
    }
    public int[] GetIndexes(int index)
    {
        int[] indexes = new int[Rank];
        switch (Ordering)
        {
            case IndexOrdering.ColumnMajor:
                {
                    for (int i = Rank - 1; i >= 0; i--)
                    {
                        // div = index/shape[i]
                        // indexes[i] = index - shape[i]*div
                        // index = div
                        index = Math.DivRem(index, _shape[i], out indexes[i]);
                    }
                    return indexes;

                }
            case IndexOrdering.RowMajor:
                {
                    for (int i = 0; i < Rank; i++)
                    {
                        // div = index/shape[i]
                        // indexes[i] = index - shape[i]*div
                        // index = div
                        index = Math.DivRem(index, _shape[i], out indexes[i]);
                    }
                    return indexes;
                }
            default:
                throw new NotSupportedException();
        }
    }

    public T this[params int[] indexes]
    {
        get => _data[GetIndex(indexes)];
        set => _data[GetIndex(indexes)] = value;
    }

    public override string ToString()
    {
        return $"[{string.Join(",", _data)}]";
    }
}

To test the code with a rank=3 matrix (3D matrix), I used:

static void Main(string[] args)
{
    const int L0 = 4;
    const int L1 = 3;
    const int L2 = 2;

    var A = new Matrix<int>(L0, L1, L2);

    Console.WriteLine($"rank(A)={A.Rank}");
    Console.WriteLine($"size(A)={A.Size}");
    Console.WriteLine($"shape(A)=[{string.Join(",", A.Shape)}]");

    int index = 0;
    for (int i = 0; i < L0; i++)
    {
        for (int j = 0; j < L1; j++)
        {
            for (int k = 0; k < L2; k++)
            {
                A[i, j, k] = index++;
            }
        }
    }
    Console.WriteLine($"A=");

    for (int i = 0; i < L0; i++)
    {
        for (int j = 0; j < L1; j++)
        {
            Console.Write($"[{i},{j},..] = ");
            for (int k = 0; k < L2; k++)
            {
                Console.Write($"{A[i, j, k]} ");
            }
            Console.WriteLine();
        }
        Console.WriteLine();
    }

    for (int idx = 0; idx < A.Size; idx++)
    {
        var ixs = A.GetIndexes(idx);
        Console.WriteLine($"A[{string.Join(",", ixs)}] = {A.Data[idx]}");
    }

}

with output

rank(A)=3
size(A)=24
shape(A)=[4,3,2]
A=
[0,0,..] = 0 1
[0,1,..] = 2 3
[0,2,..] = 4 5

[1,0,..] = 6 7
[1,1,..] = 8 9
[1,2,..] = 10 11

[2,0,..] = 12 13
[2,1,..] = 14 15
[2,2,..] = 16 17

[3,0,..] = 18 19
[3,1,..] = 20 21
[3,2,..] = 22 23

A[0,0,0] = 0
A[0,0,1] = 1
A[0,1,0] = 2
A[0,1,1] = 3
A[0,2,0] = 4
A[0,2,1] = 5
A[1,0,0] = 6
A[1,0,1] = 7
...
A[3,1,1] = 21
A[3,2,0] = 22
A[3,2,1] = 23
Grouse answered 19/11, 2023 at 17:25 Comment(0)
E
0

If you have a grid of Size=(Length,Width,Height), which corresponds to Coordinates=(X,Y,Z), to get the index, you use the following equation:

Index = X + Length * (Y + (Width * Z))

Make sure that whatever solution you end up using covers all cases of (Length,Width,Height) and/or (X,Y,Z).

Let's plug in a few examples.

Size = (Length=2,Width=3,Height=4)

That means an array of Size = 2 * 3 * 4 = 24

Now lets say we have

Coordinates = (X=1,Y=1,Z=2)

in a 0 indexed coordinate system. To calculate the Index, we get:

Index = X + Length * (Y + (Width * Z))

Index = 1 + 2 * (1 + (3 * 2))

Index = 15

15 is less than 23, which is the last index in a 0 index array of size 24, so we are good!

Now lets just test the max coordinate:

Coordinates = (X=1,Y=2,Z=3)

Index = X + Length * (Y + (Width * Z))

Index = 1 + 2 * (2 + (3 * 3))

Index = 23

Wow! Look at that! We got 23, which we've already established is the last index of our array! Perfect!

Eachern answered 17/12, 2023 at 3:22 Comment(5)
Note: In StackOverflow there is no "above" because display order is inidividually configurable. For me e.g. your answer is topmost beneath the question. Also, "top answer" is an unreliable reference, since votes can change. I have removed this and a few other things that would only have attracted the wrong kind of attention to your post.Freeloader
Using Length for maximal x and Width for maximal y is a little counterintuitive to me, (xMax, yMax do not suffer that risk of disagreement on intuitiveness) but lets go with it. Using return (z * xMax * yMax) + (y * xMax) + x from https://mcmap.net/q/365951/-how-to-quot-flatten-quot-or-quot-index-quot-3d-array-in-1d-array and xMax=Length=2 and yMax=Width=3 and x=1, y=1, z=2 from your first example I get (2 * 2 * 3) + (1 * 2) + 1, i.e. 15; the same as your calculation. With your second example x=1, y=2, z=3, I get (3 * 2 * 3) + (2 * 2) + 1, i.e. 23; also the same as your calculation.Freeloader
The same applies to the calculations from other answers, I am sure. This does not surprise me, because according to en.wikipedia.org/wiki/Distributive_property your calculation Index = X + Length * (Y + (Width * Z)) is indeed identical to Index = X + Length * Y + Length * Width * Z, i.e. apart from your choice of naming (the helpfulness of which is at least questionable to me), the same calculation as in other answers. You might hence want to explain the new insight your solution provides (especially in the light of what I deleted from your answer).Freeloader
On top of that the calculation from currently accepted https://mcmap.net/q/365951/-how-to-quot-flatten-quot-or-quot-index-quot-3d-array-in-1d-array is even using the same application of distributiveness as your calculation x + WIDTH * (y + DEPTH * z) (again apart from naming...).Freeloader
xMax and yMax is counterintuitive, not LENGTH and WIDTH, because when I read xMax, I read it as the MAXIMUM value x can be, which is a zero based index. It would be LENGTH-1 = xMax. That is NOT the right answer! The second highest voted answer in this post is also just plain WRONG. The third comment in the post points this out. The third highest voted answer in this post is the first correct and non-confusing answer on this post, however its mostly a correction. It doesn't give examples and is not thorough. Length, Width and Height are very clear about what they represent.Eachern

© 2022 - 2024 — McMap. All rights reserved.