epitaque
epitaque

Reputation: 93

What is the best way to shift a multidimensional array in Javascript?

I'm trying to create a javascript function that shifts an array right x units any up y units. It must keep the array size the same, and it must call unloadChunk for elements that are getting shifted off the multidimensional array. Here is my current implementation:

function shift(x, y) {
    if (x > 0) {
        for (var i = 0; i < chunks.length; i++) {
            for (var j = chunks[i].length - 1; j >= 0; j--) {
                if(j + x > chunks[i].length - 1 && chunks[i][j]) {
                  unloadChunk(i, j);
                } 
                if (j < x) {
                    chunks[i][j] = null;
                }
                else {
                    chunks[i][j] = chunks[i][j - x];
                }
            }
        }
    }
    else if (x < 0) {
        for (var i = 0; i < chunks.length; i++) {
            for (var j = 0; j < chunks[i].length; j++) {
                if(j + x  < 0 && chunks[i][j]) {
                  unloadChunk(i, j);
                } 
                if (j - x >= chunks[i].length) {
                    chunks[i][j] = null;
                }
                else {
                    chunks[i][j] = chunks[i][j - x];
                }
            }
        }
    }
    if (y > 0) {
        for (var i = 0; i < chunks.length; i++) {
            if (i + y >= chunks.length) {
                for (var j = 0; j < chunks.length; j++) {
                  if(i - y < 0 && chunks[i][j]) {
                    unloadChunk(i, j);
                  }
                  chunks[i][j] = null;
                }
            }
            else {
                for (var j = 0; j < chunks.length; j++) {
                    if(i - y < 0 && chunks[i][j]) {
                      unloadChunk(i, j);
                    }
                    chunks[i][j] = chunks[i + y][j];
                }
            }
        }
    }
    else if (y < 0) {
        for (var i = chunks.length - 1; i >= 0; i--) {
            if (i + y < 0) {
                for (var j = 0; j < chunks.length; j++) {
                     if(i - y > chunks.length - 1 && chunks[i][j]) {
                       unloadChunk(i, j);
                     }
                    chunks[i][j] = null;
                }
            }
            else {
                for (var j = 0; j < chunks.length; j++) {
                     if(i - y > chunks.length - 1 && chunks[i][j]) {
                       unloadChunk(i, j);
                     }
                    chunks[i][j] = chunks[i + y][j];
                }
            }
        }
    }
}

If you're having trouble understanding exactly what I want the shift function to do, take a look at this fiddle and look at the html output. My attempt at creating the shift function works, but it has 10 for loops. My question was, is there a more efficient, less verbose way to do this?

Upvotes: 2

Views: 5368

Answers (4)

Thomas
Thomas

Reputation: 12657

it must call unloadChunk for elements that should be set to null

it's not a good idea to mutate an Array, while you're iterating over the same Array. So change unloadChunk() to not change chunks, but return the new value.

in my case I'm filling all the null values with new values.

then why do you bother to insert the null-values in the first place? why don't you just insert the new values?

//one-dimensional shift
function shift(arr, offset, callback){
    var i, j, len = arr.length;
    if(len && (offset |= 0)){
        typeof callback === "function" || (callback = function(v){return v});
        if(offset < 0){
            for(i=-offset,j=0; i<len;)arr[j++]=arr[i++];
            while(j<len)arr[j]=callback(null,j++,arr);
        }else if(offset){
            for(i=len-offset,j=len; i>0;)arr[--j]=arr[--i];
            for(i=0; i<j;++i)arr[i]=callback(null,i,arr);
        }
    }
    return arr;
}

//two dimensional shift
function shift2d(matrix, offsetX, offsetY, callback){
    var i, len = matrix.length, tmp, fn;
    offsetY |= 0;
    offsetX |= 0;
    if(len && matrix[0].length && (offsetY || offsetX)){
        typeof callback === "function" || (callback = function(v){return v});
        fn = function(val,j){ return callback(null, [i,j], matrix) };
        tmp = {length: matrix[0].length};
        offsetY && shift(matrix, offsetY, function(){return tmp});
        for(i = 0; i < len; ++i){
            if(matrix[i] === tmp){
                matrix[i] = Array.from(tmp,fn);
            }else{
                shift(matrix[i], offsetX, fn);
            }
        }
    }
    return matrix;
}

and the code:

var chunks = [[5, 3, 1], [9, 2, 5], [2, 3, 7]];

console.log(chunks);
console.log(shift2d(chunks, -1, 1));
console.log(shift2d(chunks, 1, -1, computeValue));

function computeValue(value, index, array){
    console.log("value: %o index: %o, is chunks: %o", value, index, array === chunks);

    //return the new value for this field
    return JSON.stringify(index);
    return Math.random();

    //with ES6 array destructuring:
    var [row,col] = index;
    return row*3 + col;
}

shift() and shift2d() expect as the last argument an (optional) callback function that returns the new values. For consistency reasons, I pass the same argument as Array.map and the others

  • value: always null, only there for consistency reasons
  • index: the "index" that is accessed. For shift2d this is an array of indices (take a look at array destructuring)
  • array: the current array/matrix. Don't mess around with this while it gets changed. The main reason to pass this argument, is to check wich Array you're currently processing.

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386756

This proposal uses

For better visibillity, I replaced the null value with 1000, 2000, 3000 and 4000.

function shift(x, y) {
    while (x > 0) {
        chunks.forEach(function (a) {
            a.pop();
            a.unshift(1000);
        });
        x--;
    }
    while (x < 0) {
        chunks.forEach(function (a) {
            a.shift();
            a.push(2000);
        });
        x++;
    }
    while (y > 0) {
        chunks.unshift(chunks.pop().map(function () { return 3000; }));
        y--;
    }
    while (y < 0) {
        chunks.push(chunks.shift().map(function () { return 4000; }));
        y++;
    }
}

function print(msg) {
    document.body.innerHTML += '<p>' + msg + '</p>';
}

function printarr(arr) {
    for (var i = 0; i < arr.length; i++) {
        print(JSON.stringify(arr[i]))
    }
}

var chunks = [[5, 3, 1], [9, 2, 5], [2, 3, 7]];

print("chunks: " + JSON.stringify(chunks));
shift(1, 0);
print("shifting right 1. chunks: "); printarr(chunks);
shift(-1, 0);
print("shifting left 1. chunks: "); printarr(chunks);
shift(0, 1);
print("shifting up 1. chunks: "); printarr(chunks);
shift(0, -1);
print("shifting down 1. chunks: "); printarr(chunks);

Upvotes: 3

guest271314
guest271314

Reputation: 1

If interpret Question correctly, you can use Array.prototype.forEach(), Array.prototype.splice()

var chunks = [[5, 3, 1], [9, 2, 5], [2, 3, 7]];
// `x`: Index at which to start changing array
// `y`: An integer indicating the number of old array elements to remove
function shift(arr, x, y, replacement) {
  arr.forEach(function(curr, index) {
    // The elements to add to the array, beginning at the start index.
    // start index: `x` 
    curr.splice(x, y, replacement) 
  });
  return arr
}
// e.g.,
shift(chunks, -1, 1, null);
console.log(chunks);

Upvotes: 0

Yukul&#233;l&#233;
Yukul&#233;l&#233;

Reputation: 17102

You can use pop(), push(), shift(), unshift() Array methods

var chunks = [
  [5, 3, 1],
  [9, 2, 5],
  [2, 3, 7]
];

function shiftDown(){
  chuck.pop();
  chuck.unshift(Array(3));
}

function shiftUp(){
  chuck.shift();
  chuck.push(Array(3));
}

function shiftRight(){
  chuck.foreach(function(v){
    v.pop();
    v.unshift(null);
  })
}

function shiftLeft(){
  chuck.foreach(function(v){
    v.shift();
    v.push(null);
  })
}

Upvotes: 0

Related Questions