Poh Zi How Poh Zi How - 5 months ago 20
Javascript Question

Generating 3D array from 2D array

I'm trying to create a music game where I have to generate a 3D array from a basic 2D array. The plan was to copy and paste the 2D array 4 times into the 3D array before modifying it, as shown:

var note3base = [
["C", "E", "G"],
["C#", "E#", "G#"],
["Db", "F", "Ab"],
["D", "F#", "A"],
["Eb", "G", "Bb"],
["E", "G#", "B"],
["F", "A", "C"],
["F#", "A#", "C#"],
["Gb", "Bb", "Db"],
["G", "B", "D"],
["Ab", "C", "Eb"],
["A", "C#", "E"],
["Bb", "D", "F"],
["B", "D#", "F#"],
["Cb", "Eb", "Gb"]
];

var note3 = new Array(4);

for (h=0;h<note3.length;h++){
note3[h] = note3base;
} //creates 4 copies of note3base in a 3d-array to be modified

for (i=0;i<note3[0].length;i++){
note3[1][i][1] = flat(note3[1][i][1]); //minor
note3[2][i][1] = flat(note3[2][i][1]);
note3[2][i][2] = flat(note3[2][i][2]); //dim
note3[3][i][2] = sharp(note3[3][i][2]); //aug
} //how did everything become the same?


The problem now seems to be that the
for
loop seems to apply the method to every single array (0 through 3).

The desired output for note3[0][1] would be C E G, note3[1][1] would be C Eb G, note[2][1] would be C Eb Gb, note[3][1] would be C E G#.

Any help is greatly appreciated!

I've included the (working) sharp and flat methods below for reference:

function sharp(note){
var newnote;
if (note.charAt(1) == "#"){
newnote = note.replace("#", "x");
} else if (note.charAt(1) == "b"){
newnote = note.replace("b", "");
} else {
newnote = note + "#";
}
return newnote;
}

function flat(note){
var newnote;
if (note.charAt(1) == "#"){
newnote = note.replace("#", "");
} else {
newnote = note + "b";
}
return newnote;
}

Answer

The problem is that when you assign a variable equal to an array like this:

someVar = someArray;

...it doesn't make a copy of the array, it creates a second reference to the same array. (This applies to all objects, and arrays are a type of object.) So after your loop, where you've said:

for (h=0;h<note3.length;h++){
  note3[h] = note3base;

...all of the elements in note3 refer to the same underlying array.

To make an actual copy, you can manually copy all of the elements across using a loop, or you can use the .slice() method to make a copy for you:

for (h=0;h<note3.length;h++){
  note3[h] = note3base.slice();
}

But that will only solve half of the problem, because note3base itself contains references to other arrays, and .slice() will just copy these references. That is, although note3[0] and note3[1] (and 2 and 3) will refer to different arrays, note3[0][0] and note3[1][0] and note3[2][0] and note3[3][0] will refer to the same ["C", "E", "G"] array. (And so forth.)

You need what's called a "deep copy". You could do it with a nested loop:

for (h=0;h<note3.length;h++){
  // create this element as a new empty array:
  note3[h] = [];
  // for each 3-note array in note3base
  for (var k = 0; k < note3base.length; k++) {
    // make a copy with slice
    note3[h][k] = note3base[k].slice();
  }
}

Having said all that, I think an easier way to do the whole thing would be instead of having a note3base variable that refers to an array, make it a function that returns a new array:

function makeNote3Array() {
  return [
    ["C", "E", "G"],
    ["C#", "E#", "G#"],
    ["Db", "F", "Ab"],
    ["D", "F#", "A"],
    ["Eb", "G", "Bb"],
    ["E", "G#", "B"],
    ["F", "A", "C"],
    ["F#", "A#", "C#"],
    ["Gb", "Bb", "Db"],
    ["G", "B", "D"],
    ["Ab", "C", "Eb"],
    ["A", "C#", "E"],
    ["Bb", "D", "F"],
    ["B", "D#", "F#"],
    ["Cb", "Eb", "Gb"]
  ];
}

Because the function uses an array literal it will create a brand new array of arrays every time it is called. So then you can do the following, with no need for .slice() or nested loops:

var note3 = new Array(4);
for (h=0;h<note3.length;h++){
  note3[h] = makeNote3Array();
}