2

Here is my code :

class ArrayND {
    /**
     * @param {Array<number>} ranks
     */
    constructor (ranks) {
        this.coords = {}
        this._ranks = ranks
    }

    /**
     * @param {number|string} defaultValue
     */
    init (defaultValue) {
        // How to init this.coords with defaultValue given this._ranks ?
    }
}

What i want to achieve :

const myArrayND = new ArrayND([3, 2])
myArrayND.init(5)
/*
myArrayND.coords should be :
{
    0,0: 5,
    0,1: 5,
    1,0: 5,
    1,1: 5,
    2,0: 5,
    2,1: 5
}
*/
myArrayND.coords[[2, 1]] // returns 5

My question is : How to implement the init() function, when the ArrayND can be built with any ranks ?

example :

const firstArray = new ArrayND([])
firstArray.init(5)
// firstArray.coords contains {}

const secondArray = new ArrayND([5])
secondArray.init('aze')
/* secondArray.coords contains {
    0: 'aze',
    1: 'aze',
    2: 'aze',
    3: 'aze',
    4: 'aze',
} */
const thirdArray = new ArrayND([2, 3])
thirdArray.init(2)
/* thirdArray.coords contains {
    0,0: 2,
    0,1: 2,
    0,2: 2,
    1,0: 2,
    1,1: 2,
    1,2: 2,
} */
const anotherExample = new ArrayND([1, 2, 3])
anotherExample.init(1)
/* anotherExample.coords contains {
    0,0,0: 1,
    0,0,1: 1,
    0,0,2: 1,
    0,1,0: 1,
    0,1,1: 1,
    0,1,2: 1,
} */
...

I don't want myObject.coords to contain a multi-dimensional Array because I had other issues to access and set values in a multi-dimensional Array : see JS multidimentional array modify value. It works pretty well, but the ugly switch case is very repelling.

Instead of having

multiDimArray = [
    [
        [1],
        [2],
        [3]
    ],
    [
        [4],
        [5],
        [6]
    ]
]
multiDimArray[0][1][2] // returns 6

Problem is access :

function setValue(indexArray, value) {
    switch (indexArray.length) {
        case 0:
           multiDimArray = value
        case 1:
           multiDimArray[indexArray[0]] = value
        case 2:
           multiDimArray[indexArray[0]][indexArray[1]] = value
        case 3:
           multiDimArray[indexArray[0]][indexArray[1]][indexArrat[2]] =value
        ...
        // Very ugly and isn't truly dynamic
    }
}

I want

multiDimArray = {
    0,0,0: 1,
    0,0,1: 2,
    0,0,2: 3,
    0,1,0: 4,
    0,1,1: 5,
    0,1,2: 6,   
}
multiDimArray[[0, 1, 2]] // returns 6

That way i can access and set the values dynamically with a function like that : (which was done with an ugly switch case in the case of the Array of Arrays)

function setValue(indexArray, value) {
    multiDimArray[indexArray] = value
    // much better than before.
}

So i know the name 'ArrayND' can be a bit misleading, but it's not actually an Array, it's a flat object containing the coordinates of the n-dimensional Array.

Community
  • 1
  • 1
Bad Wolf
  • 81
  • 6

2 Answers2

2

If i have understood correctly you can always generate your N dimension array and initialize it as the following. The last parameter is the initialization value. and the previous is the dimensions array where the length of it is the number of dimensions and the items are sizes of each dimension of the array to be created.

To instantiate multi dimensional array we have to have an Array.prototype.clone() method at hand in order to prevent references to get copied. So it goes like this.

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};

Array.prototype.clone = function(){
  return this.reduce((p,c,i) => (p[i] = Array.isArray(c) ? c.clone() : c, p),[])
}

function arrayND(...n){
  return n.reduceRight((p,c) => c = (new Array(c)).fill(true).map(e => Array.isArray(p) ? p.clone() : p ));
}


var m = arrayND(...[3,4,2,3],5);
m[0][2][1][2] = "five";

console.log(JSON.stringify(m,null,2));

console.log(m.getNestedValue(2,3,1,2));

You can of course do the same with Array.prototype.reduce() but i prefer right to left for the sake of abstraction

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 1
    btw, you can use now console.log. congrats to 1k :) – Nina Scholz May 20 '16 at 07:46
  • 1
    @Nina Scholz: Hey thank you for both... console.log if big relief. – Redu May 20 '16 at 07:50
  • 1
    just a small hint, you can omit stringify. – Nina Scholz May 20 '16 at 07:55
  • 1
    Thanks i tried it like console.log(m) but it doesn't show like JSON.stringify does.. Just shows the bottom level and the above levels are like briefed. But anyways getting rid of this document.write thing is big relief really. – Redu May 20 '16 at 08:02
  • 1
    This i a really nice implementation of a n-dimensional Array, but that's not what i want, I want a flat Object containing the coordinates of a n-dimensional Array. I'll edit my question to be clearer. – Bad Wolf May 22 '16 at 08:34
  • 1
    @Bad Wolf: ok i got your point.. I still believe using a proper ND array is the right way to store the coordinates. But later sometime today i will modify my Object.prototype.getNestedValue() @ http://stackoverflow.com/a/37072111/4543207 to work with arrays as well so that you can access them with no switches whatsoever. – Redu May 22 '16 at 09:19
  • 1
    @Bad Wolf: OK have a look at my modified code to see if it works for you. Arguments in the `m.getNestedValue(2,3,1,3)` are the 4D array indices and it returns the value located at that particular location. The arguments can of course be provided dynamically like `m.getNestedValue(x,y,z,w)` or `m.getNestedValue(...[x,y,z,w])` – Redu May 22 '16 at 09:30
  • 1
    That's a very good code, reduced to only one line, nice and working ! :) – Bad Wolf May 22 '16 at 15:20
  • @Bad Wolf: Thanks.. I am sure you will need the `m.setNestedValue(2,3,1,3)` too. I have it but i have to modify it slightly to cover all use cases. I am currently working on it. Will post it here. – Redu May 22 '16 at 15:21
  • That's a very good code, reduced to only one line, nice and working ! :) After further performance tests I don't think i'm going to take the getNestedValue function, eventually i'm getting back to the switch case, unless you can improve it. Here are my results after a 1 000 000 iterations test (these getters are going to get called a lot in my application). - Array of Arrays with switch case getter : ~40ms - flat Object with myObject[indexArray] getter : ~800ms - your solution (Array of Arrays with recursive getter) : ~2300ms – Bad Wolf May 22 '16 at 15:25
  • @Bad Wolf: well ok but two points here.. 1) I would expect getNestedValue() to be slower than switch case.. but it's to fetch the value from an indefinite depth. You can not set a switch case range changing dynamically for an unknown dimension array (this was one condition that you have mentioned). In getNestedValue it's just a matter of providing enough number of arguments. 2) ~2300msec is crazy long man how many dimensions do you have in your array? – Redu May 22 '16 at 15:32
  • 1
    Here is the test i'm doing : var array = arrayND([1,2,3,4],5) function test () { let result const start = Date.now() for (let i = 0; i < 1000000; i++) { result = array.getNestedValue(0,1,2,3) } console.log(Date.now() - start) //~execution time return result } I don't think this test is very accurate, but it gives an approximate idea of the function performance. Anyway your code is very good, but this part of my application is critical performance wise, so I think i'll stick to the switch case getter. But i'll take your arrayND function for instantiation ! :) – Bad Wolf May 22 '16 at 15:47
  • Well ok now i got it. Surely getNestedValue can not beat the others in speed, especially the lookup table one (the flat object with properties of array indices) nothing can beat LUT one in speed but i suppose it takes up even more memory than the ND array itself permanently besides you have to update it every time you modify the array. BTW to access the array items you always have the `eval()` option too but i would avoid that. – Redu May 22 '16 at 16:05
  • 1
    I have found a bug with your arrayND function, but i'm not sure how to solve it. When i do var a = arrayND(1, 2, 3, 4); and then i try to set a value like a[0][0][0] = 12; Then access a[0][1][0] returns 12, when it should be 4.... – Bad Wolf May 27 '16 at 08:54
  • 1
    @BadWolf thank you very much good catch...It's because i have overlooked and the inner arrays got multiplied by their references. To be able to correct this i had to invent the Array.prototype.clone() method. See above the modified code. It's cool actually. – Redu May 27 '16 at 10:51
0

If you want to fill your custom object with defaultValue for each 'x,y' attributes, and retrieve the value with [x, y], you can do:

init ( defaultValue ) {
    for ( var x = 0; x < this._ranks[ 0 ]; x += 1 ) {
        for ( var y = 0; y < this._ranks[ 1 ]; y += 1 ) {
            this.coords[ String( x ) + ',' + String( y ) ] = defaultValue;
        }
    }
}

and use a getter like:

get valueAt( _coords ) {
    return this.coords[ _coords.join( ',' ) ];
}
CLEm
  • 26
  • 5
  • Yes, but that would only work for an ArrayND of rank 2. My question is, how to do it with rank n (n can be supposably infinite). That would make an 'infinite' number of for loop. Can a for loop be created dynamically, or is this possible using recursion ? ... If I have 'x,y,z' attributes for example, or only a 'x', your code doesn't work anymore. – Bad Wolf May 19 '16 at 10:08