4

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;
}
1
  • Note that your flat() function needs an else if case like your sharp() function, to cover double-flats. Commented Jun 14, 2016 at 9:07

2 Answers 2

3

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();
}
Sign up to request clarification or add additional context in comments.

1 Comment

that's a really clear explanation! with an even better solution to boot! thanks alot!
3

TL;DR, do this:

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

Explanation:

The problem is coming from the difference between passing something'by value' and 'by reference' in Javascript.

When you assign a primitive value to a variable, like a = "string";, and then assign that to another variable, like b = a;, the value is passed to b 'by-value': its value is assigned to b, but b references a different part of memory. There are now two "string" values in the memory, one for a, and one for b.

a = "string";
b = a;
a = "gnirts";
console.log(b);   // "string" 

This is not how it works for non-primitive types, such as arrays. Here the value is passed to b 'by reference', meaning that there is still only one [1, 2, 3] array in the memory, and both a and b are pointing at it. This means that is you change an element in a, it will change for b as well, because they reference the same array in memory. So you get this:

a = [1, 2, 3];
b = a;
a[0] = "hello";
console.log(b);   // ["hello", 2, 3]

b[0] has changed because it references the same location in memory as a[0]. To get around this problem, we need to explicitly make a copy of note3base when passing it to another variable, rather than just passing it by reference. We can do this with note3base.slice(0) as above.

Edit: read more here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.