Help! not creating a "tracks" property and setting value to empty array []

Tell us what’s happening:
Why does this not work, I know its different to the solution code, but it is not creating an empty array to complete the solution. The only thing missing is if the property “tracks” doesn’t exist in an object, apparently my code is not creating a new property, “tracks”, and setting it to be an empty array. i hope that makes sense

Your code so far


// Setup
var collection = {
    "2548": {
      "album": "Slippery When Wet",
      "artist": "Bon Jovi",
      "tracks": [ 
        "Let It Rock", 
        "You Give Love a Bad Name" 
      ]
    },
    "2468": {
      "album": "1999",
      "artist": "Prince",
      "tracks": [ 
        "1999", 
        "Little Red Corvette" 
      ]
    },
    "1245": {
      "artist": "Robert Palmer",
      "tracks": [ ]
    },
    "5439": {
      "album": "ABBA Gold"
    }
};
// Keep a copy of the collection for tests
var collectionCopy = JSON.parse(JSON.stringify(collection));

// Only change code below this line
function updateRecords(id, prop, value) {
  
  if (prop == "tracks" && value != ""){
    if (collection[id][prop] != true){
            collection[id][prop] = [];
        }}
  if (prop == "tracks" && value != "") {
        if (collection[id][prop]){ 
          collection[id][prop].push(value)    
      } else if (prop !== "tracks" && value != ""){
        collection[id][prop] = value;
      } else if (value == "") {
       delete collection[id][prop];

}
        return collection;

      }

// Alter values below to test your code
updateRecords(5439, "artist", "ABBA");

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36.

if (prop == "tracks" && value != "")

You use the same condition for both your if statements.

what effect does that have?

Here your code is tidied, look at the brackets, the else if statement for when prop is not "tracks" is never executed

So I’ve said it before and I’ll say it again: before you write a single line of code, get familiar with the idea of pseudocode and use it!

Pseudocode is, simply put, the logic without the language. I’m not worrying about what looping mechanism or what array method or what reverse lookahead magic i might need to do. I’m not worrying about writing the How of the thing, I’m solely focusing on the What.

In general, there are a few steps to the logic, and knowing them can make pseudocode easier.

  • First, within your function, eliminate error cases early. In this case, there aren’t any explicit errors, but here’s one to consider – what if you’re to delete a property from a record that doesn’t exist? Might be an error, might not.
  • Second, are there “universal” cases, something that, with a certain set parameters, will have the same effect every time? In this case, the empty value means to delete that property, regardless of what type it is. So, with an empty value, we can eliminate that as a question later.
  • Third, what is the least amount of work to the next universal case? In this case, it’s adding or updating a property (really, the only remaining case), and before we can do that, we need to check what our prop is – the ONLY one that we need special handling for would be prop == 'tracks'. So handle THAT case next, thus leaving us at the next universal case.
  • Keep whittling the cases down like this, specialized at one level to general at the same level, to a narrower and narrower focus, until all the possible function cases have been handled.

Lot of theory, but what would it look like in practice? When I talk pseudocode, I’m actually a hack - for me, pseudocode is my own natural language. I write it in a manner that I can understand, which lets me process the (sometimes ugly) spec given, and make sure I understand it by rewording it. So here’s how the language of this might work, in my own words:

function updateRecords(id, prop, value) {
  /***
   * So the updateRecords function is doing a few things:
   *   - removing attributes from a given record,
   *   - adding or updating attributes on a given record,
   *   - returning the entire record collection
   * In the case of adding or updating, there is a unique case. While most prop
   *   values are simple "primitives" (strings or numbers or boolean values),
   *   'tracks' is a unique property, in that it is an array. In this case, we have
   *   a couple different things to do:
   *   - check if the `tracks` property array exists, and create it if it doesn't,
   *   - add the given value to the tracks array.
   *
   *   This is the end of the commentary. The next comment block is pseudo-
   *     code, and is something I usually replace in the solution. 
   ***/

  /**
  * if an object with the id doesn't exist, we should error out early.
  *                 (exiting the function early)
  *   
  * if value is blank, then delete the object.prop and return the collection.
  *                 (exiting the function early)
  *
  * if prop is 'tracks', then
  *    if the object doesn't have a `tracks` property, create one.
  *    either way, we can now add 'value' to the 'tracks' array
  *    and return the collection (exiting the function early) 
  * 
  * // at this point, we've handled all the errors, top-level general case, and
  * //  specialized cases. So now, we can handle the last of the sub-general
  * //  cases: adding or updating any other property on the object.
  * 
  * set object.prop to the value, and return the collection
  * 
  * // This ends the function, and has handled all possible cases in the given
  * //  spec.
  **/
}

I tend to be very wordy in my pseudocode, because I tend to talk a LOT. lol But the fact remains, I try to build my logic with no language-specific features. It should apply in most (not all, but most) languages. I can take this same pseudocode and run it on PHP, or perl, or Ruby, and the functionality is exactly the same.

In our case, we want to make it JS, but each step is broken down enough to be easily coded in a line or two of javascript. For me, this is a logical first step. It clarifies the tasks in my head, and gets me started thinking about the how (the stuff I haven’t done in my comments is going on in my head).

2 Likes

In the case of the record collection challenge, this doesn’t happen. This case does have to be explicitly handled in the contacts challenge however.

I had a good read of this and really appreciate the time you gave this. I am trying to be a thorough but efficient developer so these approach methodologies really help.

What I am particularly struggling with at the moment is the ordering/sorting of all these functionalities or as you call them, general/specific/special cases.

So sometimes I’m writing code and due to some kind of RETURN statement for example, the subsequent code is not being carried out. This is causing me some confusion when structuring “if/else” statements for example, its hard to explain in words exactly what I mean but its like I’m trying to create multiple conditions in my mind but it doesn’t seems to be translating on screen.

So for example I’ll say to myself “If x is true AND y is true then check this for this, then if true execute z, but if x is FALSE then do this.” This is my logic but it doesn’t seem to be coming out in my code or its not possible.

Probably makes no sense but thanks for reading if you got this far! I guess I’ll just get there in the end.

Sure it is possible, you just have to nest the statements correctly
You can do it in various ways
This is an example, I have taken your words for this and followed your words exactly, but you can nest things in a different way, or choose to start from something else
Just remember that only one thing of a if/else chain is executed, the first one that have the condition true, and that you can’t test inside an if statement for its condition to be false (for example in the next one if you have a if (!x) inside the first statement that one will never execute because the condition to arrive there is for x to be true)

if (x && y) {
  if (this for this) {
      z 
   }
} else if (!x) {
   this
}
1 Like

What you say does make sense, and it’s a frustration we all go through.

Yes, return statements break you out of a loop, or an if, or whatever. A common error new coders go through is writing some sort of loop (like a for(), and inside that, putting a return statement. This sounds great, but what happens is the return statement exits the loop, and the function, on the very first pass.

And that logic as you’ve written it out is perfectly valid, and pretty easily codeable in a number of languages. It’s a matter of making the transition from “people language” to “lobotomized-medicated-five-year-old idiot computer language”.

The fact is, the human brain does many many calculations and logical inferences with no real effort, but computers just don’t. Barring a perfect artificial intelligence system, your code will do exactly what you tell it. Which is great, but its not always easy to break our thoughts down to the level it may require. Possible, but not easy.

Taking the logic and breaking it down, as @ieahleen has done, is a very useful way of looking at things like this. But one point to clarify on this:

Just remember that only one thing of a if/else chain is executed, the first one that have the condition true, and that you can’t test inside an if statement for its condition to be false (for example in the next one if you have a if (!x) inside the first statement that one will never execute because the condition to arrive there is for x to be true)

may not be entirely accurate. When looking at a complex if statement (one with more than one term being checked for true or false), the type of connector determines whether the next term will be checked.

if ( x && y && z){
  ...
}

The above code block will check x – if x is false the if block IMMEDIATELY stops. If x is true, then it will check y. In the event that ANY term in an and (&&) statement, evaluated right-to-left, returns false, the statement exits and the loop does not process.

if ( !x && y && z ) {
  ...
}

In the above, the checked order is exactly the same. Each part, right to left, MUST evaluate to true. But the only time !x will evaluate to true is if x is false. so this statement evaluates to “If x is NOT true and y is true and z is true”. Again, if x is true, then the other two never get processed at all. They don’t have to.

if ( x || y || z) {
  ...
}

This one is exactly the reverse. It will also start from the left, but at the first member to return true, the entire statement evaluates to true, and the if statement executes. If x evaluates as true, then the other two will never be checked, they don’t need to be.

Beyond that, things can get really complex and ugly, combining ands and ors, but for the purposes of clarifying @ieahleen’s point, I think that’ll do. :wink:

1 Like

thanks I will try this in the future

thanks again for this deep conceptual explanation! So a return statement kicks you out of anything? So if you need to get multiple results you need to always use a loop then as the loop supercedes the return statement I’ guessing?

A return statement always returns from the enclosing function, so it will exit a loop too. For example, this loop will only ever run once:

function foo() {
  for (i = 0; i < 10; i++) {
      return "foo";
  }
}

As soon as it hits the return statement, the loop exits and the function returns “foo”, without ever going through the loop again.

Keep in mind a return exits only the immediately enclosing function, so returning from a callback function only exits the callback. For example:

function foo() {
  let x = [-1, 5, -10, 3, 4];
  let pos = x.filter(function(n) { return n > 0 });
  console.log(pos);
  return pos;
}

In this case, the return inside the filter function is only returning from the anonymous filter function, not foo(). Only when it hits the return at the very end does it return from foo().

1 Like

As @chuckadams points out, return is a very final way to break out of a loop, and its enclosing function. return is saying “This is the final evaluation of the function, so send THIS value back to whoever called me.” And sometimes, that’s what you want - In the case of the records collection, when the delete is done and nothing else applies (if you have no value for value, then all you’re doing is deleting), then returning early can save processing time and headaches.

If, however, you want to make sure every pass of a loop runs, then store whatever values from that loop into a variable or variables, then AFTER the loop, explicitly return those values (assuming that was the point of the loop, to go for an accumulated loop “total” or full loop string.

Early return is a very confusing idea at first, but when you work it out, it’s really really powerful.