Loop is stuck for some reason and then finally returns undefined

314 views Asked by At

This is the problem I am trying to solve: Given: an array containing hashes of names

Return: a string formatted as a list of names separated by commas except for the last two names, which should be separated by an ampersand.

Example:

list([ {name: 'Bart'}, {name: 'Lisa'}, {name: 'Maggie'} ])
// returns 'Bart, Lisa & Maggie'

list([ {name: 'Bart'}, {name: 'Lisa'} ])
// returns 'Bart & Lisa'

list([ {name: 'Bart'} ])
// returns 'Bart'

list([])
// returns ''

Note: all the hashes are pre-validated and will only contain A-Z, a-z, '-' and '.

Here is my code:

var finalName;
var notFinal;

function list(names){
  var finalNames = names.forEach(returnNames);
        console.log(typeof finalNames);

  function returnNames() {
    for (var i = 0; i<names.length; i++) {
      var nameValue = Object.keys(names[i]).map(key => names[i][key])
    }
  }

  for(var i = 0; i<finalNames.length; i++) {
    if (finalNames.length / i == 1) {
      finalName = "& " + finalNames[i]; 
    }
    else {
      notFinal = finalNames[i] + ", ";
    }
  }

  console.log(notFinal + finalName);
}

list([{name: 'Bart'},{name: 'Lisa'},{name: 'Maggie'},{name: 'Homer'},{name: 'Marge'}])

It is stuck in the loop and finally gives an error:

TypeError: Cannot read property 'length' of undefined
    at list
    at /home/codewarrior/index.js:30:1
    at Object.handleError
        <anonymous>

How do I fix it?

6

There are 6 answers

0
Gerard On BEST ANSWER

You can simplify your code like below.

function list(names){
  //Determine length of array (= number of names)
  const len = names.length;
  //Use a simple for loop to go through the array
  let newNames = "";
  for (i=0; i<len; i++) {
    newNames += names[i].name;
    if ( i<len-2 ) newNames += ", "
      else if ( i<len-1 ) newNames += " & "
  }
  console.log(newNames);
}

list([{name: 'Bart'},{name: 'Lisa'},{name: 'Maggie'},{name: 'Homer'},{name: 'Marge'}])
list([{name: 'Bart'}, {name: 'Lisa'}]);
list([{name: 'Bart'}]);

0
Gustavo A Olmedo On

That's because forEach doesn't return anything, try using a map function instead.

var finalNames = names.map(returnNames);
4
Juan Elfers On

As they've already pointed out, Array.prototype.forEach returns undefined. Instead, you could use .map for this, modifying your returnNames function

var finalName;
var notFinal;

function list(names){
  // Changed .forEach with .map
  var finalNames = names.map(returnNames);
  console.log(typeof finalNames);

  function returnNames(person) {
    // If you only need to get the object values, use Object.values instead of Object.keys
    return Object.values(person);
  }

  for(var i = 0; i < finalNames.length; i++) {
    // Added + 1 because i could never be equal to the array length
    // Note that you'll need to make 1 or 2 more changes before this code works as expected
    if (finalNames.length / (i + 1) == 1) {
      finalName = "& " + finalNames[i]; 
    }
    else {
      notFinal = finalNames[i] + ", ";
    }
  }

  console.log(notFinal + finalName);
}

list([{name: 'Bart'},{name: 'Lisa'},{name: 'Maggie'},{name: 'Homer'},{name: 'Marge'}])

0
Nithish On

I have used reduce in order to find the final string to display the names as per your requirement. Hope this helps.

formatString = (list) => {
    let length = (list || []).length;
    return list.reduce((result, obj, index) => {
        result += obj.name || "";
        if(index + 1 === length - 1) {
            result += " & ";
        } else if(index !== length - 1) {
            result += ", ";
        }
return result;
    }, "")
}

let list = [{"name":"Bart"},{"name":"Lisa"},{"name":"Maggie"},{"name":"Homer"},{"name":"Marge"}]
console.log(formatString(list))

list = [ {name: 'Bart'}, {name: 'Lisa'} ]
console.log(formatString(list))

list = [ {name: 'Bart'} ]
console.log(formatString(list))

list = []
console.log(formatString(list))

0
slumbergeist On

There are two problems with your code. If you go through your code

function list(names){
  var finalNames = names.forEach(returnNames);
    console.log(typeof finalNames);

  function returnNames() {
    for (var i = 0; i<names.length; i++) {
    var nameValue = Object.keys(names[i]).map(key => names[i][key])
   }
 }

In the forEach you are passing returnNames as a callback. A forEach will iterate over every element of the array. Passing each element to the callback provided and executing it.

As you can see, inside your callback you are again iterating over the entire list with the for a loop. So effectively, for each element, you are iterating over the whole list again increasing the complexity to O(n2)

Second issue, as other answers has pointed out, you are returning nothing from the forEach callback. Hence, the value of finalNames becomes undefined. So, when you run this statement

  finalNames.length / i == 1

length property can't be read.

0
Travis Tay On
function returnNameListString(namelist){
    if (namelist.length){
        return
    }else if(namelist.length===1){
        return toString(namelist)
    }else{
        nameString = namelist[0]
        for(var i=1; i<(namelist.length-1);i++){
            nameString = nameString + ", " + namelist[i]
        }
        nameString = nameString + " & " + namelist[namelist.length-1]
        return nameString
    }

}

Try this! Declare name as a list ["tom", "dick", "harry"]