Replace(), replacing a value not specified - javascript

I've been learning Javascript via FCC for 6 weeks now, and decided to spend a week learning, and playing around with methods to get really close and comfortable with using them in loops, and with statements. Just playing around with strings, I was trying to replace the string "I like milk", with "I like silk". My code accomplishes this, and I understand it, but I decided to add an || operator, and the outcome bewilders me. If i say if(x[i]=== 'm' || x[i] === 'I', it doesn't replace 'I', but replaces 'm' If i leave it the way it is in my original code, it produces the string 'I lise milk', even though 'k' was never mentioned. What is going on?
let x = 'Hello I like milk';
let y = '';
for(let i = 0; i < x.length; i++) {
if(x[i] === 'm' || 'I') {
y = x.replace(x[i], 's' )
}
}
console.log(y)

ok, there are a couple of gotchas that make your code interesting
x[i] === 'm' || 'I' is always true, because 'I' is truthy - if that's not clear, you can test this with if ('I') { console.log('Merry Christmas') }
your loop produces a new string with one replacement every loop, i.e. it produces the same result as no loop and
y = x.replace(x[x.length - 1], 's')
the replace will replace the first matching character - the last character in your string is k, so the replace will change the first matching k to s
so, the result of your code is
'Hello I lise milk'
Hope that helps clear things up!
btw, the easiest way to replace all ms and Is with an s:
console.log(x.replace(/[mI]/g, 's'))

So you basically are running into an issue with the way your code is flowing.
Let's start with issue one: Why is the m being overwritten but not the y?
So basically you have var x and y that you are updating throughout your loop.
The way your code works is basically: If the letter equals m or I replace it with an s.
The problem you are going to have here however is you never store the value that has since been updated. So when it loops again it is taking the default value of x (which hasn't actually been updated) and is writing it to y. This is going to overwrite the value each time.
We can demo this by simply logging inside of the loop.
let x = 'Hello I like milk';
let y = '';
for(let i = 0; i < x.length; i++) {
if(x[i] === 'm' || x[i] === 'I') {
y = x.replace(x[i], 's' )
console.log(y)
}
}
So if you run the above, you will see two lines being outputted.
Hello s like milk
Hello I like milk
The m is the only one being printed however because the log is after the final update.
So the next issue: Why is replacing that k?
As another commented posted, "I" is always going to be truthy. Truthy basically means the value isn't null, undefined, 0 etc. "I" has a value which means it will always be true.
So is the K being replaced? Well if we run that code, the final letter being checked and replaced is a k. .replace only replaces (by default) the first instance of that letter it comes across. In your case, that K is the first K that is seen.
So to fix it, don't check on "I" check: x[i] === "I"
EDIT: To answer your question about why the last letter matters
So the last letter matters here because you have what basically equates to:
if("I"){}
So that above snippet, contrary to what you may think actually means if this has a value, which because I is a valid character, will always report true. So for your loop, everything is actually being checked, regardless of the character because I is always going to be true.
Here is an easy way to check it:
Lets say I have an array of integers from 1 to 10. I have (pseudo code):
if array value < 10 OR I
Print that value
With the way you are currently thinking, you would expect it to stop printing if the values are less than 10 right? Nope! Once again because I is always truthy and we are saying OR the value I (true) so we are going to always get a print!
Code example:
var x = [0, 1, 2, 3, 4 , 50, 60, 70, 80];
for(var i = 0; i < x.length; i++){
if(x[i] < 10 || "I") {
console.log(x[i]);
}
}
See? Everything prints out! Even though half of the values are over 10 they are still printing out. Why? Because "I" is always true! Now let's edit that a bit to make it a bit more strict in checking:
let x = [0, 1, 2, 3, 4 , 50, 60, 70, 80];
for(var i = 0; i < x.length; i++){
if(x[i] < 10 || x[i] === "I") {
console.log(x[i]);
}
}
See? Much better :)

Related

Check length between two characters in a string

So I've been working on this coding challenge for about a day now and I still feel like I haven't scratched the surface even though it's suppose to be Easy. The problem asks us to take a string parameter and if there are exactly 3 characters (not including spaces) in between the letters 'a' and 'b', it should be true.
Example: Input: "maple bread"; Output: false // Because there are > 3 places
Input: "age bad"; Output: true // Exactly three places in between 'a' and 'b'
Here is what I've written, although it is unfinished and most likely in the wrong direction:
function challengeOne(str) {
let places = 0;
for (let i=0; i < str.length; i++) {
if (str[i] != 'a') {
places++
} else if (str[i] === 'b'){
}
}
console.log(places)
}
So my idea was to start counting places after the letter 'a' until it gets to 'b', then it would return the amount of places. I would then start another flow where if 'places' > 3, return false or if 'places' === 3, then return true.
However, attempting the first flow only returns the total count for places that aren't 'a'. I'm using console.log instead of return to test if it works or not.
I'm only looking for a push in the right direction and if there is a method I might be missing or if there are other examples similar to this. I feel like the solution is pretty simple yet I can't seem to grasp it.
Edit:
I took a break from this challenge just so I could look at it from fresh eyes and I was able to solve it quickly! I looked through everyone's suggestions and applied it until I found the solution. Here is the new code that worked:
function challengeOne(str) {
// code goes here
str = str.replace(/ /g, '')
let count = Math.abs(str.lastIndexOf('a')-str.lastIndexOf('b'));
if (count === 3) {
return true
} else return false
}
Thank you for all your input!
Here's a more efficient approach - simply find the indexes of the letter a and b and check whether the absolute value of subtracting the two is 4 (since indexes are 0 indexed):
function challengeOne(str) {
return Math.abs(str.indexOf("a") - str.indexOf("b")) == 4;
}
console.log(challengeOne("age bad"));
console.log(challengeOne("maple bread"));
if there are exactly 3 characters (not including spaces)
Simply remove all spaces via String#replace, then perform the check:
function challengeOne(str) {
return str = str.replace(/ /g, ''), Math.abs(str.indexOf("a") - str.indexOf("b")) == 4;
}
console.log(challengeOne("age bad"));
console.log(challengeOne("maple bread"));
References:
Math#abs
String#indexOf
Here is another approach: This one excludes spaces as in the OP, so the output reflects that. If it is to include spaces, that line could be removed.
function challengeOne(str) {
//strip spaces
str = str.replace(/\s/g, '');
//take just the in between chars
let extract = str.match(/a(.*)b/).pop();
return extract.length == 3
}
console.log(challengeOne('maple bread'));
console.log(challengeOne('age bad'));
You can go recursive:
Check if the string starts with 'a' and ends with 'b' and check the length
Continue by cutting the string either left or right (or both) until there are 3 characters in between or the string is empty.
Examples:
maple bread
aple brea
aple bre
aple br
aple b
ple
le
FALSE
age bad
age ba
age b
TRUE
const check = (x, y, z) => str => {
const exec = s => {
const xb = s.startsWith(x);
const yb = s.endsWith(y);
return ( !s ? false
: xb && yb && s.length === z + 2 ? true
: xb && yb ? exec(s.slice(1, -1))
: xb ? exec(s.slice(0, -1))
: exec(s.slice(1)));
};
return exec(str);
}
const challenge = check('a', 'b', 3);
console.log(`
challenge("maple bread"): ${challenge("maple bread")}
challenge("age bad"): ${challenge("age bad")}
challenge("aabab"): ${challenge("aabab")}
`)
I assume spaces are counted and your examples seem to indicate this, although your question says otherwise. If so, here's a push that should be helpful. You're right, there are JavaScript methods for strings, including one that should help you find the index (location) of the a and b within the given string.
Try here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#instance_methods

How to find total possible values from length and characters?

I'm totally not a Math whiz kid here, but have put together a function with the great help of StackOverflow (and a lot of trial and error) that generates a random serial number from a Formula, group of Letters/Numbers, and array (so as to not duplicate values).
So, my current formula is as follows:
$.extend({
generateSerial: function(formula, chrs, checks) {
var formula = formula && formula != "" ? formula : 'XXX-XXX-XXX-XXX-XXX', // Default Formula to use, should change to what's most commonly used!
chrs = chrs && chrs != "" ? chrs : "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", // Default characters to randomize, if not defined!
len = (formula.match(/X/g) || []).length,
indices = [],
rand;
// Get all "-" char indexes
for(var i=0; i < formula.length; i++) {
if (formula[i] === "-") indices.push(i);
}
do {
rand = Array(len).join().split(',').map(function() {
return chrs.charAt(Math.floor(Math.random() * chrs.length));
}).join('');
// Rebuild string!
if (indices && indices.length > 0)
{
for(var x=0; x < indices.length; x++)
rand = rand.insert(indices[x], '-');
}
} while (checks && $.inArray(rand, checks) !== -1);
return rand;
}
});
Ok, so, what I need to be able to do is to find total possible values and make sure that it is possible to generate a unique serial number before actually doing so.
For example:
var num = $.generateSerial('XX', 'AB', new Array('AB', 'BA', 'AA', 'BB'));
This will cause the code to do an infinite loop, since there are no more possibilties here, other than the ones being excluded from the extension. So this will cause browser to crash. What I need to be able to do here is to be able to get the number of possible unique values here and if it is greater than 0, continue, otherwise, don't continue, maybe an alert for an error would be fine.
Also, keep in mind, could also do this in a loop so as to not repeat serials already generated:
var currSerials = [];
for (var x = 0; x < 5; x++)
{
var output = $.generateSerial('XXX-XXX-XXX', '0123456789', currSerials);
currSerials.push(output);
}
But the important thing here, is how to get total possible unique values from within the generateSerial function itself? We have the length, characters, and exclusions array also in here (checks). This would seem more like a math question, and I'm not expert in Math. Could use some help here.
Thanks guys :)
Here is a jsFiddle of it working nicely because there are more possible choices than 16: http://jsfiddle.net/qpw66bwb/1/
And here is a jsFiddle of the problem I am facing: Just click the "Generate Serials" button to see the problem (it continuously loops, never finishes), it wants to create 16 serials, but 16 possible choices are not even possible with 2 characters and only using A and B characters: http://jsfiddle.net/qpw66bwb/2/
I need to catch the loop here and exit out of it, if it is not able to generate a random number somehow. But how?
The number of possible serials is len * chrs.length, assuming all the characters in chrs are different. The serial contains len characters to fill in randomly, and chrs.length is the number of possible characters in each position of that.

Recursion and Loops - Maximum Call Stack Exceeded

I'm trying to build a function that adds up all the numbers within a string... for example, 'dlsjf3diw62' would end up being 65.
I tried to be clever and put together a recursive function:
function NumberAddition(str) {
var numbers='1234567890';
var check=[];
str=str.split[''];
function recursive(str,check) {
if (str.length==0)
return check;
else if (numbers.indexOf(str[0])>=0)
{
for (i=0;i<str.length;i++){
if (numbers.indexOf(str[i])<0)
check.push(str.slice(0,i));
str=str.slice(i);
return recursive(str,check);
}
}
else
str.shift();
return recursive(str,check);
}
You'll see that I'm trying to get my numbers returned as an array in the array named check. Unfortunately, I have a maximum call stack size exceeded, and I'm not sure why! The recursion does have a base case!! It ends once str no longer has any contents. Why wouldn't this work? Is there something I'm missing?
-Will
You can achieve the same thing with a far easier solution, using regular expressions, as follows:
var str = 'dlsjf3diw62';
var check = str.match(/\d+/g); // this pattern matches all instances of 1 or more digits
Then, to sum the numbers, you can do this:
var checkSum = 0;
for (var i = 0; i < check.length; i++) {
checkSum += parseInt(check[i]);
}
Or, slightly more compact:
var checkSum = check.reduce(function(sum, num){ return sum + parseInt(num) }, 0);
The reason your recursion doesn't work is the case where you do enter the for loop, because you've found a digit, but the digits continue to the end of the string. If that happens, the return inside the for loop never happens, and the loop ends. After that, the .shift() does not happen, because it's in that else branch, so you return re-process the same string.
You shouldn't solve this particular problem that way, but the code makes a good example of the anti-pattern of having return statements inside if bodies followed by else. Your code would be clearer (and would work) if it looked like this:
function recursive(str, check) {
if (str.length == 0)
return check;
if (numbers.indexOf(str[0]) >= 0) {
// Find the end of the string of digits, or
// the end of the whole thing
for (var i = 0; i < str.length && numbers.indexOf(str[i]) >= 0; i++);
check.push(str.slice(0, i));
str = str.slice(i);
return recursive(str, check);
}
// A non-digit character
str.shift();
return recursive(str, check);
}
In that version, there are no else clauses, because the two if clauses always involve a return. The for loop is changed to simply find the right value of "i" for the subsequent slicing.
edit — one thing this doesn't fix is the fact that you're pushing arrays into your "check" list. That is, the substring "62" would be pushed as the array ["6", "2"]. That's not a huge problem; it's solved with the addition of a .join() in the right place.

checking if digit exists on particular place in string

I have a string that starts with "TT" and ends with six digits(ex. "TT012345", "TT012000, TT329001). The string is always formatted like this and I need to check if the last digit in this string is of a certain value.
Say I have the string "TT032970". In this case I'd like to get a match on this string since the last digit is zero and the digit before that is a seven(I'm looking for 7).
The string "TT037000" should also be a match but "TT0329701" shouldn't(since it isn't all zeroes to the right of the seven(the "last" 7 in the string)).
I was thinking of using a set of nested if's using substr() to check all places of the string for zeroes and if it isn't a zero in position n, then I check if the digit I'm looking for exists in position n.
My code is repetitive and I'm all for being efficient.
This is what I got so far(that works but only checks the last place of the string and the second last place):
var lastDigit = [3, 7, 8], tda = document.querySelectorAll('td a'), i, j;
function checkArray(num) {
"use strict";
for (j = 0; j < lastDigit.length; j++) {
if (num === lastDigit[j]) {
return true;
}
}
}
for (i = 0; i < tda.length; i++) {
if ((parseInt(tda[i].textContent.substr(8, 1), 10) === 0 && checkArray(parseInt(tda[i].textContent.substr(7, 1), 10))) || checkArray(parseInt(tda[i].textContent.substr(8, 1), 10))) {
tda[i].style.background = "rgb(255, 144, 255)";
amountOfTickets.push(tda[i]);
}
}
I'm positive there's a great way of checking the string for trailing zeroes and check the first non-zero digit before the zeroes. However, I'm really bad with loops and I just can't figure out how.
I'm very keen on figuring it out myself but I need a head start. I'd rather take a detailed explanation on how to do it than just the "answer".
If anything else seem off I'd gladly listen to improvements.
Thanks in advance!
To get the first digit before the zeros at the end of a string, you may use a regular expression :
+string.match(/(\d)0*$/)[1]
Example 1 :
var string = "TT032970";
var digit = +string.match(/(\d)0*$/)[1];
console.log(digit); // logs 7
Example 2 :
console.log(["TT012345","TT012000","TT329001","TT032970"].map(function(string){
return +string.match(/(\d)0*$/)[1]
})); // logs [5, 2, 1, 7]
Demonstration
Obviously, from the other answers, a regular expression will be much simpler than your loops. Moreover, any nested loop solution will be difficult to work, as you don't know how many levels deep you have to look. (Is there one zero? Two? Five?)
This regex is quite simple:
/(\d)0+$/
If you do a match on that with your string, you should get either null if it doesn't match (e.g. "TT0329701") or a two-element array if it does (e.g. "TT037000" will return ["7000", "7"].)
That should be enough for your to build your own solution upon.
Best of luck.
The first thing I though about is something like this (depends on whether I understood your problem correctly):
function lookFor(str, digit) {
//validate input...
if (str.length != 8) return false;
if (str[0] != "T" && str[1] != "T") return false;
//start at the end and move to the left as long as there are zeros
//the first non-zero element must be our digit, else return false
for (var i = str.length-1; i>0; --i) {
if (str[i] !== "0") {
return str[i] === digit;
}
}
}
lookFor("TT012000", "2") --> true
lookFor("TT012000", "3") --> false
But I guess the regex solution is probably more compact than this one.

Function not calling itself... enough

Each string in an array is a number, for example array1 = ["1296", "12", "27"];
For each string above, if possible to divide by 6 evenly without remainders, I want to do so at least once, then if the result is still longer than 2 characters, repeat. Then replace the string in the same position, such that the array would become ["36", "2", "27"];
so far, my code partly works.
w=0;
function divideBySix(){
if ((array1[w] / 6) == (Math.floor(array1[w] / 6))) {
var temp = array1[w] / 6;
array1[w] = temp.toString();
if (array1[w].length < 3) {
w++;
}
divideBySix();
}
The function successfully divides the first string by 6 once, and then calls itself again and again until the result is within 2 chars long. At that point, it should continue calling itself, and do the same to the next string of the array. But it doesn't do the next string. I don't know why it stops after finishing the first string. So the array looks like this ["36", "12", "27"]; Also, w successfully gets incremented. So I know its getting at least that far...
The function you give has unbalanced { }. When I add one at the end and run it, I get the result you say you want — ["36", "2", "27"]. There must be something else wrong, or you have not copied the code correctly.
In order to understand the operation, I added this to the beginning of divideBySix:
console.log(w, array1.toString());
i think you could just go with the modulo operator, if this is what you wanted to achieve
if(array1[w] % 6 == 0) doSomething()
and to solve your current problem you could introduce a second function ; for me it works with :
function divideBySix(array){
for(var i = 0; i < array.length; i++){
array[i] = divideNumber(array[i], 0);
}
}
function divideNumber(nr, ct){
if((ct < 1 || nr > 99) && nr%6 == 0 ) return divideNumber(nr/6, ct+1);
else return nr;
}
var array1 = ["1296", "12", "27"];
divideBySix(array1);
alert(array1);
​
When I test the code, that's what it does:
http://jsfiddle.net/Guffa/x4fNP/

Categories