Javascript: Map function weird behaviour - javascript

I am using the below code to identify if a character is duplicated, if it is then I replace with a specific char, else another char.
This code works
function dup(str) {
return str
.toLowerCase()
.split("")
.map((index, nonsense, s) => {
console.log(s);
return s.indexOf(index) == s.lastIndexOf(index) ? "(" : ")";
})
.join("");
}
But I do not understand why, the variable 'nonsense' makes it work. If you remove that unused var I get errors.
How can an unused var affect how map works?

The problem is with the order of the arguments and the value at specific locations
function dup(str) {
return str
.toLowerCase()
.split("")
.map((char, index, self) => { // the order is char, index and the current array in the 3rd argument
console.log(self);
return self.indexOf(char) == s.lastIndexOf(char) ? "(" : ")";
})
.join("");
}
If you wish to remove the issue with unused variable (for example, say with eslint) use the _ or prefix with _
function dup(str) {
return str
.toLowerCase()
.split("")
.map((char, _, self) => { // the order is char, index and the current array in the 3rd argument
console.log(self);
return self.indexOf(char) == self.lastIndexOf(char) ? "(" : ")";
})
.join("");
}
EDIT
Though unrelated,a more optimal way to do it would be to use Set to deduplicate.
function dup(str) {
return Array.from(new Set(str
.toLowerCase()
.split(""))
.join("");
}

This has less to do with .map() and more to do with receiving arguments.
The function passed to .map is automatically passed 3 arguments (which you are calling index, nonsense, and s here). In JavaScript, you are not required to specifically capture any of them with argument names, but if you want to use the second or third one, you will need to provide some argument name(s) for the ones you are going to skip over to indicate that you are interested in the third argument.
Having said that, the arguments passed to .map() (in order) are: element, index, array and your names suggest that you believe it's: index, element, array. So a better naming convention would be as shown below:
function dup(str) {
return str
.toLowerCase()
.split("")
.map((char, index, ary) => {
console.log(ary);
return ary.indexOf(index) == ary.lastIndexOf(index) ? "(" : ")";
})
.join("");
}
dup("The quick quick brown fox.");

in your code index represent the current char, nonsense represents the indexOf that char at s array. if you remove nonsense index will be represent the char and s will be represent the indexOf that char. that cause the error because you are trying to do s.indexOf(index) while s is of type number
try to do it like this
function dup(str) {
return str
.toLowerCase()
.split('')
.map(s => {
console.log(s)
return str.indexOf(s) == str.lastIndexOf(s) ? '(' : ')'
})
.join('')
}

From the docs:
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
As you can see, the map function takes up to three params, where the second and third are optional. Your variables index, nonsense and s are filling in for the current value, the actual index and the array. Remove nonsense and your s variable becomes the current index of map, instead of your array.
That is the expected behavior.

Related

Array manipulation error with regex - Kata 6 Codewar

In theory it should transform a given array to camel case. I don't understand what is wrong
function toCamelCase(str){
if(str.length === 0) return ""
let array = str.split(/([_-])/);
array.forEach(word =>{
word == "-" ? word.replace("") : word.charAt(0).toUpperCase()
})
return array
}
The .replace() method doesn't modify the word variable, it instead returns a new modified string. So your code is producing new values within the loop but doesn't do anything with those values. Moreover, word here is a value and not a reference to your array values, so you can't modify them directly from within your forEach() loop and expect it to modify the string values from your array. You instead need to create a new array, with each element transformed, which can be done by using .map() and returning the new value:
function toCamelCase(str) {
const array = str.split(/[_-]/);
return array.map((word, i) => {
return i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
}).join("");
}
console.log(toCamelCase("this-is-some-text"));
Note that you can remove the capturing group from your .split() to remove the _ and - chars from your array so that you don't need to remove them when you map.
Note that for something like this, if you're already using regular expressions in your .split(), you might consider using .replace() with a replacement function, for example, something like:
function toCamelCase(str) {
return str.replace(/-\w/g, ([,m]) => m.toUpperCase());
}
console.log(toCamelCase("this-is-some-text"));
word == "-" ? word.replace("") : word.charAt(0).toUpperCase() is just a ternary statement floating in space. This code isn't altering any variables. It's equivalent to:
if(word == "-"){
word.replace("")
}
else{
word.charAt(0).toUpperCase()
}
Really, you don't need to mess with arrays if you make use of .replace()'s callback function.
function toCamelCase(str) {
// Match underscore or dash followed by a letter, case-insensitively
// Store the letter in a capture group; $1 in this case
return str.replace( /[_-]([a-z])/gi, function( matches ) {
// Uppercase the letter
return matches[ 1 ].toUpperCase()
} );
}
console.log( toCamelCase( 'to-be_Camel_cased' ) );

What's wrong with the first element in HOF in JS?

Here I have a string with repeated chars,
I want to get the string without repetition, so I used map with specific index, but I don't know what's wrong with the 1st repeated char, when I used slice(i+1,1) all thing was good, but when I used slice(i,1) also all thing was good except the first repeated char.
The output of the 1st code is : elzero
The output of the 2nd code is : eelzero
What's the problem?
Here's the two codes:
let myString = "EElllzzzzzzzeroo";
let elzero = myString
.split("")
.map(function(ele, i = 1, myString) {
console.log(i);
if (myString[i] === myString[i + 1]) {
return myString[i + 1].slice(i + 1, 1);
} else {
return myString[i];
}
})
.join("");
console.log(elzero);
// Elzero
And here's the second:
let myString = "EElllzzzzzzzeroo";
let elzero = myString
.split("")
.map(function(ele, i = 1, myString) {
console.log(i);
if (myString[i] === myString[i + 1]) {
return myString[i].slice(i, 1);
} else {
return myString[i];
}
})
.join("");
console.log(elzero);
// EElzero
Your first code block only works because your .slice() happens to be returning empty strings. You really don't need to use .slice() at all, since all it does is generate an empty string which is then removed when you use .join(). The .slice() call does NOT update/remove an element like .splice() on an array does.
In your second code block, you're doing:
myString[i].slice(i, 1);
If i is 0 then you are on the first character in your string, so myString[0] is "E". Here, .slice(0, 1) says, give me the string that starts at index 0 up to, but not including index 1. This results in "E" being returned.
For all of your subsequent calls myString[i] gives you back one character (so it only has one index, 0), but you're trying to use .slice() to get a portion of your string from indexes that don't exist as i is bigger than your index. Moreover, i is bigger than your second argument, so when i is 1, you're asking to get get the string portion from index 1 (which doesn't exist) up to but not including 1, which results in an empty string. When i is 2, its a similar situation, which results in an empty string.
What you should be doing is removing the use of .slice() altogether, and instead return an empty string when you want to omit the character:
let myString = "EElllzzzzzzzeroo";
let elzero = myString
.split("")
.map(function(ele, i, myString) {
if (ele === myString[i + 1]) { // if current char (ele) equals the next char (myString[i+1]), then "remove" the current char by mapping it to an empty string (this is removed when we `.join("")`)
return "";
} else {
return ele;
}
})
.join("");
console.log(elzero); // Elzero
More concisely, you can rewrite this with arrow functions and a ternary like so. You can also use Array.from() to iterate the code points (ie: characters) within your string, and use the second argument as the mapping function:
const myString = "EElllzzzzzzzeroo";
const elzero = Array.from(
myString,
(ele, i) => ele === myString[i + 1] ? "" : ele
).join("");
console.log(elzero); // Elzero
Alternatively, using a different approach, you can use .replace() with a regular expression to remove repeated characters:
const myString = "EElllzzzzzzzeroo";
const elzero = myString.replace(/(.)\1*/g, '$1')
console.log(elzero); // Elzero
The (.) captures a character and groups it, the \1* is a backreference that matches zero or more of the grouped character repeated, and the "$1" replaces the matched characters with the singular grouped character.
Since you are returning myString[i], not myString[i+1], you have to compare myString[i] with myString[i-1]:
let myString = "EElllzzzzzzzeroo";
let elzero = myString
.split("")
.map(function(ele, i = 1, myString) {
console.log(i);
// if (myString[i] === myString[i + 1]) {
if (myString[i-1] === myString[i]) {
return myString[i].slice(i, 1);
} else {
return myString[i];
}
})
.join("");
console.log(elzero);
// EElzero

a bit clarafication on using spread and string manipulation, foreach loop

so I've wrote this function, i want to uppercase the vowels and lowercase every other letter,
problem the end result ends with the same string, I'm new to spread and for-each,
after i spread a string does it become an array?
when i manipulate letters does it suppose to become a string again with the manipulations or do i need to join it? why aren't the upper and lowercase functions don't work?
the function:
function upperCase(str) {
var vowels = "aeiou";
[...str].forEach(letter => {
if (vowels.includes(letter)) letter.toUpperCase();
letter.toLowerCase();
});
console.log(str);
}
You have several problems:
.toUpperCase and toLowerCase return the new value, they don't mutate the existing value (and strings are immutable anyway)
Even if they did mutate the existing value, they'd change the letter string in the array and not the original string
You didn't use else to toLowerCase always runs
You need to:
return a value
Use map to collect the values
Use join() to turn the array back into a string
Such:
function upperCase(str) {
const vowels = "aeiou";
const result =
[...str]
.map(
letter =>
(vowels.includes(letter))
? letter.toUpperCase()
: letter.toLowerCase()
).join("");
console.log(result);
}
upperCase("The quick brown fox jumps over the lazy dog");
You need to assign the result of your foreach to something.
function upperCase(str) {
var vowels = "aeiou";
[...str].forEach(letter => {
if (vowels.includes(letter)) letter.toUpperCase();
letter.toLowerCase();
});
console.log(str);
}
[...str] is creating an array, looping over it, preforming an action, but then not saving the resulting array to any variable at the end. You're also missing an else and/ or a return. I think a map also makes more sense in this case.
function upperCase(str) {
var vowels = "aeiou";
const result = [...str].map(letter => {
if (vowels.includes(letter)) return letter.toUpperCase();
return letter.toLowerCase();
});
console.log(result);
}
If you just want to manipulate a string you might want to use the replace function
const newString = str.toLowerCase().replace(/[a,e,i,o,u]/g, letter => letter.toUpperCase())
This first puts everything to lower case, and afterwards replaces all vowels (matching the regular expression) by their upper case versions.

begin .map(callback()) from specific index

Is it possible to run a callback function with specific start and stop indexes? I am practicing my JS and am writing a function to convert strings to camel case (from being '-' or '_' seperated) without altering the capitalization of the first word in the string. Basically, after I split the string into an array of words, I want to call .map() and start my callback on the second word in the array.
currently I have:
function toCamelCase(str){
return str.split(/\-|_/).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
}
How can I get .map() to begin at str.split(/\-|_/)[1] ?
In simple words, you can't. .map will iterate over an entire array.
You can chain .map to .slice though
function toCamelCase(str, start, stop){
return str.split(/\-|_/).slice(start, stop).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
}
Array.map() always iterates the entire array. You can use the index (2nd param in callback) to return the word without changes if the index is 0:
function toCamelCase(str){
return str.split(/\-|_/).map((word, i) => i ? word.charAt(0).toUpperCase() + word.slice(1) : word).join('');
}
console.log(toCamelCase('the_camels_toes'));
BTW - you can use a regular expression with String.replace() to to create the camel case:
function toCamelCase(str){
return str.replace(/_(\w)/g, (_, c) => c.toUpperCase());
}
console.log(toCamelCase('the_camels_toes'));

Javascript wrong outcome when crafting a custom regex camelCase solution for strings

I'm attempting a Javascript challenge who's instructions are:
Complete the method/function so that it converts dash/underscore delimited
words into camel casing. The first word within the output should be
capitalized only if the original word was capitalized.
Examples:
toCamelCase("the-stealth-warrior")
// returns "theStealthWarrior"
toCamelCase("The_Stealth_Warrior")
// returns "TheStealthWarrior"
My solution is:
function toCamelCase(str) {
console.log(str);
var camel = str.replace(/(?:^\w|[A-Z]|-\w|_\w)/g, function(letter, index) {
return index === 0 && letter === letter.toLowercase ?
letter.toLowercase : letter.toUpperCase();
}).replace(/(-|_)/g, "");
console.log(camel);
return camel;
}
and the output when using my code with the test cases is:
toCamelCase('the_stealth_warrior') did not return correct value -
Expected: theStealthWarrior, instead got: TheStealthWarrior
any ideas where this is going wrong? I feel my conditions in the ternary operator should be returning a lowercase t.
This bit of code here is causing your problem:
function(letter, index) {
return index === 0 && letter === letter.toLowercase ?
letter.toLowercase : letter.toUpperCase();
}
You probably meant to use toLowerCase(), but instead you've provided a reference to a non-existent property of letter. Since toLowercase doesn't exist, it will return undefined which will cause your conditional to always return false.
Change the line to:
function(letter, index) {
return index === 0 && letter === letter.toLowerCase() ?
letter.toLowerCase() : letter.toUpperCase();
}
How about simplifying it a bit to this:
function toCamelCase(str) {
return str.replace(/[-_](.?)/g, function(match, p1) {
return p1.toUpperCase();
})
}
document.write(toCamelCase("the-stealth-warrior") + "<br>");
document.write(toCamelCase("The_Stealth_Warrior") + "<br>");
Explanation:
[-_] Find either a - or _
(.?) Followed by any other character and put this other character in a group.
Then call .replace() on that with a custom callback using the g flag to do all matches.
The custom callback will be passed the full match as the first argument and any groups in the match as the subsequent arguments. Since what we want to convert this to is just the uppercase version of the first group, we just uppercase the second argument with return p1.toUpperCase() and then the whole match is replaced by an upper case version of the first matched group. This then converts _x to X.
This skips the leading character because there's no - or _ before it.
This skips any trailing - or _ because there's no character after it.

Categories

Resources