I came across a problem in an online course:
Write a function called vowelCount which accepts a string and returns an object with the keys as the vowel and the values as the number of times the vowel appears in the string. This function should be case insensitive so a lowercase letter and uppercase letter should count
Examples:
vowelCount('Elie') // {e:2,i:1};
the solution from the instructor came like this:
function vowelCount(str){
var splitArr = str.toLowerCase().split("");
var obj = {};
var vowels = "aeiou";
splitArr.forEach(function(letter){
if(vowels.indexOf(letter) !== -1){
if(obj[letter]){
obj[letter]++;
} else{
obj[letter] = 1;
}
}
});
return obj;
}
I understand the solution until the second "if" statement. I know that the first "if" statement is to check if the "letters" in the input string belongs to the "vowels". Then in the second "if" it is checking if the "letter is in the empty "obj" object created above, but at that line, the "obj" is empty bofore the "letter" is added to it, so what is the point for that "if". Also, why does adding this new "letter" to the object require an increment. I tried the code without increment and the object is still empty.
It's checking if you've ever seen the letter before in the loop. If you've never written to obj[letter], then when you do obj[letter], you get back the value undefined, which is falsy (treated as false by things like an if). if(obj[letter]) is checking for a truthy value (a value that isn't falsy) so that it adds to the number already stored at obj[letter] if it's there (obj[letter]++). But when it sees a falsy value like undefined, it takes the else branch and sets obj[letter] to 1 because the code knows that letter hasn't been seen before.
Just FWIW, while still entirely valid, that's fairly old-style JavaScript code (circa the ES5 standard, 2009). ES2015 added several features you'd use to solve this problem today:
function vowelCount(str){
// Use a Map to remember how many of each ltter you've
// seen. You could use an object as well, but ideally you'd
// create the object with out a prototype to avoid having
// any conflict with inherited properties from `Object.prototype`.
const counts = new Map(); // const counts = Object.create(null);
// The set of vowels
const vowels = new Set("aeiou");
// Loop through the letters
for (const letter of str) {
// If it's not a vowel...
if (!vowels.has(letter)){
// Count it
const currentCount = counts.get(letter) || 0;
counts.set(letter, currentCount + 1);
// Or using an object:
// const currentCount = counts[letter] || 0;
// counts[letter] = currentCount + 1;
}
});
return counts;
}
We can use regular expression to match vowels in a sentence.
Regular expression to match all occurrence of vowel in a string:/[aeiouAEIOU]+?/g
Below is the working code snippet:
//function that takes string as input
//function returns an object containing vowel count without case in account.
function vowelCount(input) {
//to get vowel count using string.match
var arrVowels =input.match(/[aeiouAEIOU]+?/g);
//acc=accumulator, curr=current value
return arrVowels.reduce(function (acc, curr) {
if (typeof acc[curr.toLowerCase()] == 'undefined') {
acc[curr.toLowerCase()] = 1;
}
else {
acc[curr.toLowerCase()] += 1;
}
return acc;
// the blank object below is default value of the acc (accumulator)
}, {});
}
Related
I am attempting the Isomorphic Strings problem on LeetCode and am having issues with my current solution. I'm sure there are plenty of answers on exactly how to complete this problem, but I would really prefer to finish it through my own thought process before learning the best possible way to do it. For reference, here is the problem: https://leetcode.com/problems/isomorphic-strings/?envType=study-plan&id=level-1
This is my code as it is right now:
var isIsomorphic = function(s, t) {
const map = new Map();
const array1 = [...s];
const array2 = [...t];
for (i = 0; i < s.length; i++) {
if ((map.has(array1[i]) === true) && (map.has(array2[i]) === true)) {
if (map.get(array1[i]) !== array2[i]) {
return false;
} else {
continue;
}
} else if (map.has(array1[i]) === false) {
map.set(array1[i], array2[i]);
}
}
return true;
};
It's messy but I can't figure out why it isn't giving me the desired results. Right now, it seems to always return true for any given values, even though I have the initial if statement to return false if it ever comes across previously-mapped values that don't match. Am I missing something obvious? This is my first question on SA, so I apologize if the format is wrong.
The map is set like:
map.set(array1[i], array2[i]);
The key is the character in the first string, and the value is the corresponding character in the second string. So, when iterating over a new character, checking map.has will only make sense if the character being passed is from the first string; doing map.has(array2[i]) === true)) does not test anything useful, because the second string's characters are not keys of the Map.
You need to perform two tests: that the 1st string's character corresponds to the 2nd string's character (which you're doing right), and that the 2nd string's character is not already set to a different 1st string's character (which needs to be fixed). For this second condition, consider having another Map that's the reverse of the first one - the keys are the characters from the 2nd string, and the values are the characters from the 1st string. (You don't have to have another Map - you could also iterate through the .entries of the first, check that, for every entry's value that matches the 2nd character, the entry's key matches the 1st - but that could feel a bit messy.)
Cleaning up your code some, there's also no need to turn the strings into arrays, and === true can be omitted entirely, and the i variable should be declared with let.
You also might want to check if the length of the first string is equal to the length of the second.
var isIsomorphic = function(s1, s2) {
if (s1.length !== s2.length) return false;
const map1to2 = new Map();
const map2to1 = new Map();
for (let i = 0; i < s1.length; i++) {
// Check that s1 here corresponds to s2
if (map1to2.has(s1[i]) && map1to2.get(s1[i]) !== s2[i]) {
return false;
}
// And that s2 here doesn't correspond to any other s1
if (map2to1.has(s2[i]) && map2to1.get(s2[i]) !== s1[i]) {
return false;
}
map1to2.set(s1[i], s2[i]);
map2to1.set(s2[i], s1[i]);
}
return true;
};
console.log(isIsomorphic('aa', 'bb'));
console.log(isIsomorphic('aa', 'ba'));
console.log(isIsomorphic('badc', 'baba'));
I am running through some practice problems on CodeWars right now and have stumbled upon an issue that I cannot seem to figure out. The problem is straightforward and simple enough:
My function should take a string input, and return the first character that is not repeated anywhere in the string.
For example, if given the input 'stress', the function should return 't', since the letter t only occurs once in the string, and occurs first in the string.
As an added challenge, upper- and lowercase letters are considered the same character, but the function should return the correct case for the initial letter. For example, the input 'sTreSS' should return 'T'.
If a string contains all repeating characters, it should return an empty string ("").
My code is as follows:
const firstNonRepeatingLetter = (string) => {
const letters = {};
// Object that I am assigning key/value pairs to based on the number of occurrences of each letter
string.split('')
.forEach(letter => {
letter = letter.toLowerCase();
if (letters[letter] !== undefined) letters[letter]++;
if (letters[letter] === undefined) letters[letter] = 1;
});
for (let key in letters) {
let val = letters[key];
// Iterate through object and upon finding the first value of 1, evaluate the casing of the key
if (val === 1) {
string.split('')
.forEach(letter => {
// *line16* If the letter in the string is the same as the corresponding key in uppercase, return letter
if (key.toUpperCase() === letter) return letter;
});
// Otherwise, return the key as is
return key;
};
};
return '';
};
This passes all tests except for the 'sTreSS' test. I have ran through the process with the debugger, and ironically, the *if conditional on line 16 displays a local Return value: 'T' when reaching this point. However, the forEach continues to iterate and I end up with a return value of 't', which is the next return statement in my block.
I am confounded by this and would appreciate any insight into what is causing the issue.
You could instead use Array.find.
if (val === 1) {
const findIt = string.split('').find(letter => key.toUpperCase() === letter);
return findIt || key; // if findIt is undefined (not found), return key
};
As mdn says:
There is no way to stop or break a forEach() loop other than by
throwing an exception. If you need such behavior, the forEach() method
is the wrong tool.
Early termination may be accomplished with:
A simple for loop
A for...of / for...in loops
Array.prototype.every()
Array.prototype.some()
Array.prototype.find()
Array.prototype.findIndex()
Array methods: every(), some(), find(), and findIndex() test the array
elements with a predicate returning a truthy value to determine if
further iteration is required.
As an alternative, you can use findIndex:
const findIndex = string.split('').findIndex(letter => key.toUpperCase() === letter);
return findIndex != -1
? string.split[findIndex ]
|| key;
But find() is neater :)
this block of code takes the values of the scored property and puts them in duplication object as a key and how many times the name got duplicated as a value using the || operator.
I understand that the || operator will return the first truthy value or the last value if all of the values are falsy, however, I didn't understand duplication[x]++ what does the ++ sign do exactly? and why we put the (duplication[x]=1) between parentheses
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const duplication = {};
for (let x of game.scored) {
duplication[x]++ || (duplication[x]=1) // I'm confused in this line
}
console.log(duplication);
Let's see what's happing on this line :
duplication[x]++ || (duplication[x]=1)
duplication[x]++ , first duplication[x] it will check if duplication has any with value of x, if yes then it it will perform duplication[x]++ else it will be undefined to moved to the other part of or condition
duplication[x]=1, this is a simple assignment it will assign the value 1, duplication[x] and this will create a key if not exist in the duplication object
Now if you run the below script and check the console log for each loop, it will give you clear idea what actually happing.
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const duplication = {};
let index = 0;
for (let x of game.scored) {
console.log( `INDEX : ${index} ` , x , duplication[x] ? 'FOUND , INCREMENT CURRENT VALUE WITH ++' : 'NOT FOUND, SO ASSIGN VALUE 1' );
duplication[x]++ || (duplication[x]=1)
console.log( `INDEX : ${index} \n` , duplication);
index++;
}
console.log( 'FINAL OBJECT \n' , duplication);
The non-descriptive variable names don't really help to explain the situation. Let's start of by rewriting the code with more descriptive variable names.
const game = {
score: "4:0",
scored: ["Lewandowski", "Gnarby", "Lewandowski", "Hummels"],
};
const goals = {};
for (const player of game.scored) {
goals[player]++ || (goals[player] = 1);
}
console.log(goals);
goals[player]++ increments the goals for player by 1 and returns the old value. The tricky thing in goals[player]++ is that player might not be present in goals yet. In which case undefined is returned (which is falsy). Because the value is falsy the second operand of the OR operator will be executed. (goals[player] = 1) will set the goals for player to 1.
The code is essentially counting how often a specific name is present in the game.scored array. The presence of a name symbolises a goal made by them.
A less cryptic way of writing similar code would be:
const goals = {};
for (const player of game.scored) {
if (player in goals) {
goals[player] += 1; // add 1 to the current score
} else {
goals[player] = 1; // no score present so use 1
}
}
However I usually prefer to set a default value, this way you don't have to split the logic into two actions:
const goals = {};
for (const player of game.scored) {
goals[player] ||= 0; // assign 0 if the current value is falsy
goals[player] += 1; // add 1 to the current value
}
Note that ||= is fairly new, if you write JavaScript for older browser you can use the following instead:
if (!goals[player]) goals[player] = 0;
The first part of
duplication[x]++ || (duplication[x] = 1)
^^^^^^^^^^^^^^^^
has four parts:
a variable duplication with
a property accessor x in bracket notation
a postfix increment operator ++ and
an expression for the logical OR || operator.
The second part returns undefined at the first call with an unknown property.
The try to increment this value returns NaN, because of the following operation of duplication[x] = duplication[x] + 1. The result is is a falsy value.
This forces the expression to evaluate the right hand part of logical OR.
And because the left hand part has an expression, it needs to be evaluated first with a grouping operator (). Now the assignment takes place and the result of 1 is returned to the OR.
// JavaScript Document
"use strict";
var testArray =["beau","Diane","morgan","Brittany"];
function checkCapital(value,index,array) {
if(value!==value.charAt(0).toLowerCase())
return value;
}
var capital =testArray.filter(checkCapital);
console.log(capital);
I need to check if the first letter of every value of the array is a capital using filter method. But I keep getting the whole array returned on my function.
Well, you are comparing an entire string with the lowercase equivalent of its first character, which is a problem. And you are going to return undefined when your condition is not satisfied, and a specific character if it is, which is a little strange. Your filter function should be
return value.charAt(0) !== value.charAt(0).toLowerCase()
or use the more modern
return value[0] !== value[0].toLowerCase()
and write the whole app in more modern JavaScript as follows:
const testArray = ["beau", "Diane", "morgan", "Brittany", "^&#SYMBOLS$$--"];
const capital = testArray.filter(s => s[0] !== s[0].toLowerCase());
console.log(capital);
but really, what is and is not an uppercase letter is an interesting problem in its own right. What if a word starts with a comma? A Cherokee or Devanagari letter? Letter-like symbols? If you really want to do uppercase properly, you might want to get a package like xregexp then you can write a filter function that matches XRegExp("^\\p{Lu}"). I understand this might be beyond what you are looking for at the moment, but it's a good thing to know about.
The filter() method depends on the return statement of the callback, which if returns true then filter() will include the current value to the array it will return. Just remove your if statement and put the condition on the return statement:
function checkCapital(value,index,array) {
return value !== value.charAt(0).toLowerCase();
}
Because your original callback returns true always, it would include all elements of the original array to be returned by the filter() method.
First off you are always returning value from your filter function, that is the core reason why you are getting the whole array. A filter function shold return true or false depending on whether the value should or should not be included in the output.
You have other issues with your code but try and solve them on your own.
solution :-
function checkUppercase(arr)
{
var array1 = arr.split(' ');
var newarray1 = [];
for(var x = 0; x < array1.length; x++){
newarray1.push(array1[x].charAt(0).toUpperCase()+array1[x].slice(1));
}
newarray1.join(' ');
let i=0;
if(JSON.stringify(array1) == JSON.stringify(newarray1)) {
return true
}else {
return false;
}
}
console.log(checkUppercase("solid Is Transparent"));
I'm still having a hard time to understand regex... :-/
Given strings (JavaScript-like expressions) like these...
foo[0]
foo[4][2]
foo[4][2][234523][3]
...I'm trying to deconstruct the indices in regex, so that I have
the name of the variable: foo
the single indices: fox example 4, 2, 234523 and 3 in the last example
while not accepting invalid syntax like
foo[23]bar[55]
foo[123]bar
[123]bla
foo[urrrr]
It would be nice to also ignore whitespace like foo [13] or foo[ 123 ] but that's not important.
Is that possible with regex?
I was able to extract the brackets with var matches = s.match(/\[([0-9]?)\]/g); but that includes the brackets in the result, is missing the variable name (could get around that) and also does not respect the edge cases as described above.
You'll have to use loops to extract multiple matches. Here's one way:
function run(string) {
var match;
if(match = string.match(/^([^[]+)\s*(\[\s*(\d+)\s*\]\s*)+\s*$/)) {
var variable = match[1], indices = [];
var re = /\[\s*(\d+)\s*\]/g;
while(match = re.exec(string)) {
indices.push(+match[1]);
}
return { variable: variable, indices: indices };
} else {
return null;
}
}
var strings = [
"foo[0]",
"foo[4][2]",
"foo[4][2][234523][3]",
"foo [13]",
"foo[ 123 ]",
"foo[1] [2]",
"foo$;bar%[1]",
// The following are invalid
"foo[23]bar[55]",
"foo[123]bar",
"[123]bla",
"foo[urrrr]",
];
// Demo
strings.forEach(function(string) {
document.write("<pre>" + JSON.stringify(run(string), null, 4) + "</pre>");
});
That is not possible.
You can test if it is a correct statement, and as long you know how many indices you have you can select them, but there is no way to catch a group multiple times with javascript .exec.
However the language is regular. So it would be this:
^([a-zA-Z][a-zA-Z_0-9]*)(\[[0-9]*\])*
The first group will match the variable, and the second group (with the *quantifier 0-n times) the index.
So if you want to do this I recommend to use another parsing approach:
function parse(str) {
let idx = 0;
while(str[idx+1] != '[') {
idx++;
}
let name = str.substr(0, idx+1);
let indices = [];
while(str[idx+1] == '[') {
idx++;
let startIdx = idx;
while(str[idx+1] != ']') {
idx ++;
}
console.log(idx);
indices.push(str.substr(startIdx+1, idx-startIdx));
idx++;
}
return {name,indices};
}
Here is small ES6 version of the 2 step regular expression to get the desired array:
function interpret(s) {
return (/^(\w+)\s*((?:\[\s*\d+\s*\]\s*)*)$/.exec(s) || [,null]).slice(1).reduce(
(fun, args) => [fun].concat(args.match(/\d+/g)));
}
var s = 'foo[4][2][234523][3]';
var result = interpret(s);
console.log(result);
It first gets the 2 main parts via exec(), which returns the complete match, the function name and the rest in an array (with 3 elements). Then with slice(1) it ignores the first of those three. The two others are passed to reduce.
The reduce callback will only be called once, since there is no initial value provided.
This is convenient, as it actually means the callback gets the two parts as its two arguments. It applies the second regular expression to split the index string, and returns the final array.
The || [,null] will take care of the case when the original match fails: it ensures that reduce acts on [null] and thus will return null.