Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript

Replacing characters in a string using replace() - function not recognized.

I'm working on a JavaScript exercise to look at a string of digits, then if the number is less than 5, replace it with '0', and if the number is 5 or greater, replace it with '1'.

I'm getting an error, "Cannot read property 'length' of undefined", which doesn't make sense to me. Any idea what I'm doing wrong here?

This is my code:

var numberString = '123456789';

function fakeBin(numberString) {

  for ( i = 0; i < numberString.length; i++ ) { //for the length of the argument

    var digit = numberString.indexOf(i); //define each digit so we can evaluate them
    //var fakeString = ''; //place to add results to a string

    if ( digit < 5) { //if digit is less than 5

      digit.replace(digit,'0'); //replace that digit with '0'

    } else { //if digit is 5 or greater

      digit.replace(digit,'1'); //replace that digit with '1'

    }
  }

  return numberString; //return the string with 0's and 1's

}

fakeBin();

An interactive version can be found here: https://repl.it/JssH/3

Thanks!

2 Answers

Steven Parker
Steven Parker
242,770 Points

I see a few things which may be causing problems:

  • the call of fakeBin at the bottom doesn't give it an argument to work with
  • indexing is done by placing a value in brackets after the item, the indexOf method does something else
  • the replace method generates a new string, it does not modify the one it is applied to
  • the code works on the digit, but then never uses it for anything
  • at the end, the original argument is returned unchanged

Aha.

Thank you for the very clear feedback!

I'm using variable[i] instead of indexOf(), and pushing things to an array and then converting it back to a string instead of using replace(). It SEEMS to work on repl.it when I try different string of numbers... however, I am working on a CodeWars challenge, and I when I run it, I see that one test passes, and after that: "newString.push is not a function".

??? Am I using push() incorrectly? I feel like I'm following the clear layout here: https://www.w3schools.com/jsref/jsref_push.asp

Also, I think that there must be a much more efficient way to accomplish this. Hrm.

My code now:

var numberString = '5139438248337';

var newString = []; //empty array to add digits to 

function fakeBin(numberString) {

  for ( i = 0; i < numberString.length; i++ ) { //for the length of the argument

    var digit = numberString[i]; //define each digit so we can evaluate them

    if ( digit < 5) { //if digit is less than 5


      newString.push('0'); //add '0' to the empty array 

    } else { //if digit is 5 or greater

      newString.push('1'); //add '1' to the empty array

    }

  }

  newString = newString.toString(); //convert the array to a string 

  for ( y = 0; y < numberString.length; y++ ) {

    newString = newString.replace(',',''); //take out the commas

  }

  return newString; //return the string with 0's and 1's

}

fakeBin(numberString);

Also at: https://repl.it/JssH/8

Steven Parker
Steven Parker
242,770 Points

Calling fakebin converts the global newString from an array into a string, so if you call the function more than once (as I'm sure the challenge testing does), you'll get "TypeError: newString.push is not a function" on the second call. To start fresh each time, the initialization of newString (and the variable itself, for that matter) should be moved into the function.

About efficiency, these optimizations come to mind:

  • use a regular expression in the replace to remove all commas at once without a loop
  • convert the array into a string using join (no commas to remove)
  • instead of building and converting an array, build up a string using concatenation

I'm hesitant to be too explicit until after you've passed the challenge.

I really appreciate how you can call out my mistakes without spelling out the answer for me! I am definitely learning a lot this way.

I got to this code below to pass the challenge, though I know it could be more efficient. I think it's still good practice for me for tracking where loops end and begin, and where things should go.

-moved newString variable inside the function -changed the positioning of where I converted it to a string, took out the commas, and returned it

//var numberString = '5139438248337';



function fakeBin(numberString) {

  var newString = []; //empty array to add digits to 

  for ( i = 0; i < numberString.length; i++ ) { //for the length of the argument

    var digit = numberString[i]; //define each digit so we can evaluate them

    if ( digit < 5) { //if digit is less than 5

      newString.push('0'); //add '0' to the empty array 

    } else { //if digit is 5 or greater

      newString.push('1'); //add '1' to the empty array

    } 

  }

  newString = newString.toString(); //convert the array to a string 

  for ( y = 0; y < numberString.length; y++ ) {

    newString = newString.replace(',',''); //take out the commas

  }

  return newString; //return the string with 0's and 1's

}

Whoa. Now I can see other students' answers, and many are just one line long! Whew! I can see how much learning is ahead of me now.

Steven Parker
Steven Parker
242,770 Points

Ok, now that you have passed (congratulations! :+1:), here's how I might do it:

function fakeBin(numberString) {
  var newString = "";
  for (var n of numberString) newString += n < 5 ? '0' : '1';
  return newString;
}

And if I wanted to be super compact:

var fakeBin = ns => ns.replace(/[1-4]/g, '0').replace(/[5-9]/g, '1');

Were there any smaller than that?

:heart_eyes: whoa.

I didn't know that I could put other things in the for() brackets besides a counter. I'll have to read up on that. Is there a term for what you've put in the for() brackets?

After that, if I were to guess it out, I'd say it goes something like:

"concatenate onto newString" newString += "if n is less than 5? make it zero" n < 5 ? '0' "else, make it 1" : '1';

But then how are you looping through each digit of n?

Steven Parker
Steven Parker
242,770 Points

There are 3 kinds of "for" loops. The one you know, the standard "for" with the 3 clauses, the "for...in" loop, and finally the "for...of" loop that I used here. You'll learn about the last 2 in more advanced courses, but the short explanation of "for...of" is that it steps through the values of the term after "of", and each time assigns the current value to the variable declared before "of". The rest is a ternary expression which you guessed the operation of correctly.

That is compact! Here are some other short answers, though I can't make much sense of them yet:

function fakeBin(x) {
  return x.replace(/\d/g, d => d < 5 ? 0 : 1);
}
function fakeBin(x){
  return x.split('').map(a => a < '5' ? "0" : "1").join("");
}

The shortest I can find:

fakeBin = x => x.split('').map(e => e < 5 ? 0 : 1).join('');
Steven Parker
Steven Parker
242,770 Points

Those last 2 are essentially the same with a different declaration style. They convert the string into an array (split), build a modified array (map), then convert it back into a string (join). But I really like the first one, similar idea to mine but I had forgotten that replace can take a function as the second argument.