Javascript assign and increment Obj's key value(with Reduce) - javascript

Im following a course online and one of the challenges is this:
Write a function called vowelCount that accepts a string and returns an object with each key being the vowel and the value being the number of times the vowel occurs in the string (the order of keys in the object does not matter).
vowelCount('incredible');
// {i:2, e: 2}
vowelCount('awesome');
// {a:1, e:2, o:1}
So far, I've come up with the following code, using Javascript's reduce:
function vowelCount(word) {
var vowels = ['a', 'e', 'i', 'o', 'u'];
var final = word.split('').reduce(function(obj, val, index) {
if (vowels.indexOf(val) > -1) {
//obj[val] = 0
obj[val]++;
}
return obj
}, {})
console.log(final)
}
I think I'm close but I'm having trouble wrapping my head on how to assign and increment the numerical value of the vowel key at point of checking if its a vowel. I tried instantiating the value to 0, but that only keeps the value at 1.

Use short circuit evaluation to check if the value exists, and if not use 0 instead:
console.log(vowelCount('incredible')); // {i:2, e: 2}
console.log(vowelCount('awesome')); // {a:1, e:2, o:1}
function vowelCount(word) {
var vowels = ['a', 'e', 'i', 'o', 'u'];
return word.split('').reduce(function(obj, val, index) {
if (vowels.indexOf(val) > -1) {
obj[val] = (obj[val] || 0) + 1;
}
return obj;
}, {});
}
In addition, instead of using an array of vowels and the Array.indexOf() check, you can initialize the result object with the vowels, and increment them directly:
console.log(vowelCount('incredible')); // {i:2, e: 2}
console.log(vowelCount('awesome')); // {a:1, e:2, o:1}
function vowelCount(word) {
return word.split('').reduce(function(obj, val) {
if(val in obj) {
obj[val]++;
}
return obj;
}, { a: 0, e: 0, i: 0, o: 0, u: 0 });
}

I think this is actually a bad usecase for reduce. Might just use a regular for loop and use the or operator (||) to replace undefined with 0:
function vowelCount(word){
const vowels = "aeiou";
const result = {};
for(const char of word)
if(vowels.includes(char))
result[char] = (result[char] || 0) + 1;
return result;
}
If that is too spooky you could just check if char already exists in result and set it otherwise:
function vowelCount(word){
const vowels = "aeiou";
const result = {};
for(const char of word){
if(vowels.includes(char)){
if(!result[char]) result[char] = 0;
result[char]++;
}
}
return result;
}

You were very close! It was because you were trying to increment undefined. You must set that value equal to a number on the accumulator before you increment it.
function vowelCount(word) {
var vowels = ['a', 'e', 'i', 'o', 'u'];
var final = word.split('').reduce(function(obj, val, index) {
if (vowels.indexOf(val) > -1) {
// if we have seen the vowel, we increment it. Otherwise, it is the first time.
obj[val] ? obj[val]++ : obj[val] = 1;
}
return obj
}, {})
console.log(final)
}

Related

frequentVowelCounter(word, count = {}) using recursion

I can do this problem using a for loop, but I'm having trouble using the recursive method.
I want to count how many times each vowel is called and return the most frequent vowel in an array.
If the array does not have a vowel to return is a string.
const vowel = ['a', 'e', 'i', 'o', 'u'];
frequentVowelCounter(word, count = {})
frequentVowelCounter(['cat', 'rain', 'dorm', 'apple', 'sun'])); // 'a'
I tried writing a base case:
if (word.length != vowel)
return ''
I don't think it's right, I'm just stuck.
Why would you want to make this recursive? An iterative approach works well.
You will need to loop over each word, and for each word, its letters. As you loop over each letter, you check to see if it is a vowel. If it is a vowel, you increment the frequency map i.e byLetter.
After you build the letter frequency map, you will need to invert the map into a count map i.e. byCount.
Finally, you can get the keys of the byCount map to find the max count and return the value for the corresponding entry in the map.
Note: You may have more than one letter share the same max occurrence count. This is why the result of the function in an array.
const maxFrequency = (words, letters) => {
const
letterSet = new Set(letters),
byLetter = new Map(),
byCount = new Map();
for (let word of words) {
for (let letter of word.toLowerCase().split('')) {
if (letterSet.has(letter)) {
byLetter.set(letter, (byLetter.get(letter) ?? 0) + 1);
}
}
}
for (let [letter, count] of byLetter) {
const letters = byCount.get(count) ?? new Set();
byCount.set(count, letters.add(letter));
}
return [...byCount.get(Math.max(...byCount.keys()))];
};
const
vowels = ['a', 'e', 'i', 'o', 'u'],
words = ['cat', 'rain', 'dorm', 'apple', 'sun'],
mostFrequent = maxFrequency(words, vowels);
console.log(...mostFrequent); // ['a']
If you want to do this recursively, just join all the words together and iterate through the string starting at index 0. The helper function i.e. __helperFn should never be called directly.
Note: You will need a helper function to set up and perform the recursion.
const __helperFn = (str, letterSet, frequency) => {
if (str.length === 0) {
const max = Math.max(...Object.values(frequency));
return Object.entries(frequency)
.filter(([letter, count]) => count === max)
.map(([letter]) => letter);
}
const letter = str.charAt(0);
if (letterSet.has(letter)) {
frequency[letter] = (frequency[letter] ?? 0) + 1;
}
return __helperFn(str.slice(1), letterSet, frequency);
}
const maxFrequency = (words, letters) =>
__helperFn(words.join('').toLowerCase(), new Set(letters), {});
const
vowels = ['a', 'e', 'i', 'o', 'u'],
words = ['cat', 'rain', 'dorm', 'apple', 'sun'],
mostFrequent = maxFrequency(words, vowels);
console.log(...mostFrequent); // ['a']
This should work:
let w = ["cat", "rain", "dorm", "apple", "sun"];
const vowelCounter = (words) => {
let vowels = {
a: 0,
e: 0,
i: 0,
o: 0,
u: 0,
};
const count = (char) => {
if (vowels.hasOwnProperty(char)) {
vowels[char]++;
}
};
const rekursive = (words, index = 0) => {
if (index === words.length) {
return;
}
let i = 0;
while (true) {
if (i >= words[index].length) {
break;
}
try {
count(words[index].charAt(i));
} catch (error) {}
i++;
}
rekursive(words, ++index);
};
rekursive(words);
console.log(vowels);
};
vowelCounter(w);

Sum of elements in array using recursion even if they are string

Have to create a function that return the sum of the element in the array but if the array is
["a","b","c"] // output : abc
So far I have
function calculateSumRecursion(array) {
//your code
if (array.length === 0 ) {
return 0
}
return array[0] + calculateSumRecursion(array.slice(1))
}
I found out how to calculate the sum of all numbers using recursion but when it's an array of string like
array = ["a","b","c"]
it returns me
// abc0
because of the if statement.. is there any way to say
if (array.length === 0) return nothing instead of a 0 (that work only when it's an array of number?)
You just need to return the only value in the array when the length is 1, rather than waiting until you get a length of 0. That way you are always summing compatible types (numbers or strings). Note that you still need a test for a 0 array length in case the function gets called with an empty array. In this case you need to choose what to return; as requested, it is 0.
function calculateSumRecursion(array) {
if (array.length === 0) {
return 0;
}
if (array.length === 1) {
return array[0];
}
return array[0] + calculateSumRecursion(array.slice(1))
}
console.log(calculateSumRecursion([1, 2, 3, 4, 5]));
console.log(calculateSumRecursion(['a', 'b', 'c']));
console.log(calculateSumRecursion([]));
let arr = [1,2,3,4,5] // output : abc
let sum = calculateSumRecursion(arr);
function calculateSumRecursion (arr) {
return arr.length ? arr.pop() + calculateSumRecursion(arr) : 0;
}
Slice version
let arr = [1,2,3,4,5] // output : abc
let sum = calculateSumRecursion(arr);
function calculateSumRecursion (arr) {
return arr.length ? arr[0] + calculateSumRecursion(arr.slice(1)) : 0;
}
Change return 0 to return "" which will add an empty string to the sum.
You have returned 0 when the array is empty.
Now, you are doing string operations so it is needed to return empty value (not zero) so it will be affordable to return "".
function calculateSumRecursion(array) {
return array.length === 0 ? "" : array[0] + calculateSumRecursion(array.slice(1));
}
There's a way easier way to do this:
function calculateSumRecursion(array) {
var out = array[0];
for (let i = 1; i < array.length; i++) {
out = out + array[i];
}
return out;
}
Return empty string on recursion base case. Just replace your return 0 to return ''.
const array = ['a', 'b', 'c'];
function calculateSumRecursion(array) {
if (array.length === 0) {
return '';
}
return array[0] + calculateSumRecursion(array.slice(1));
}
console.log(calculateSumRecursion(array));
If you are want to work with number also then check array length for zero as well as one.
const array = ['a', 'b', 'c', 'e'];
const array2 = [];
const array3 = [1, 2, 3];
function calculateSumRecursion(array) {
const rec =
array.length === 1
? array[0]
: array.length >= 1 && array[0] + calculateSumRecursion(array.slice(1));
return array.length === 0 ? 0 : rec;
}
console.log(calculateSumRecursion(array));
console.log(calculateSumRecursion(array2));
console.log(calculateSumRecursion(array3));

Lowercase and Uppercase are the same JS

I am creating a program that will pull up the first non repeated word within a string. While doing this I bump into a logic problem where I need to count a word in uppercase and lowercase the same but still returns the original uppercase version of the string.
Here's my codes:
function firstNonRepeatingLetter(str) {
// split the array and transform it to lowercase
str = str.split('');
// create an obj that will hold the counter of each characters
let myObj = {};
// create a new arr for all the char w/ 1 value
let uniq = [];
// iterate through the splitted array
str.forEach((char) => {
// if the obj contains the same key (char)
if(myObj.hasOwnProperty(char)){
// we add 1 to its value
myObj[char]++;
}else{
// otherwise we set the key value to 1
myObj[char] = 1;
}
});
// check the obj with 1 value,
for(let prop in myObj){
// and then push the key inside a
// new arr with only 1 counter
if(myObj[prop] === 1){
uniq.push(prop);
}
}
console.log(myObj);
// return the first elem in the arr
return uniq.length !== 0 ? uniq[0] : '';
}
firstNonRepeatingLetter('sTress') // => must return 'T' instead return 't'
firstNonRepeatingLetter('NecEssarY') // => must return 'N' instead return 'n'
The object looks like this: { N: 1, e: 1, c: 1, E: 1, s: 2, a: 1, r: 1, Y: 1 }it counts the 'N' and 'n'separately.
Any idea how to retain the actual case without affecting the functionality?
Thanks!
The first way that came to mind is to convert to upper-case for the object property names so that you can count "A" and "a" together, but then in addition to storing the count store the case of the first one found. So within your existing loop:
let upperChar = char.toUpperCase();
if(myObj.hasOwnProperty(upperChar)){
myObj[upperChar].count++; // add 1 to its count
} else {
myObj[upperChar] = { first: char, count: 1 }; // otherwise set count to 1
}
In context with your full code:
function firstNonRepeatingLetter(str) {
str = str.split('');
let myObj = {};
let uniq = [];
str.forEach((char) => {
// if the obj contains the same key (char)
let upperChar = char.toUpperCase();
if(myObj.hasOwnProperty(upperChar)){
myObj[upperChar].count++; // add 1 to its count
}else{
myObj[upperChar] = { first: char, count: 1 }; // otherwise set count to 1
}
});
for(let prop in myObj){ // check the obj with 1 value,
if(myObj[prop].count === 1){ // and then push the key inside a
uniq.push(myObj[prop].first); // new arr with only 1 counter
}
}
console.log(myObj);
return uniq.length !== 0 ? uniq[0] : ''; // return the first elem in the arr
}
console.log(firstNonRepeatingLetter('sTress')); // => must return 'T'
console.log(firstNonRepeatingLetter('NecEssarY')); // => must return 'N'
console.log(firstNonRepeatingLetter('Stress')); // => must return 't'
I suggest you convert the all the characters of the string to either lowercase or uppercase first.
var res = str.toLowerCase();
That way it won't count the characters separately.

Combining some array items

I'm kind of new to functional programming and I try to do the following: Let's say I have an array of values: ['a','b-','c'] and I want that every item which ends with a '-' is merged with the following array entry: ['a','b-c'].
Of course I can do this by making a for-loop:
var test = ['a', 'b-', 'c'], result = [];
for (var i=0;i<test.length;i++) {
var curr = test[i];
if (curr.endsWith('-')) {
curr += test[i+1];
i++;
}
result.push(curr);
}
But how can one do this without the for loop?
To be honest, the way you programmed is probably the most efficient way to do this.
However, here's another option:
var test = ['a', 'b-', 'c'],
result = test.join().replace(/-,/g, '').split(',');
console.log(result);
This joins all elements into a string: 'a,b-,c', removes all occurrences of '-,' > 'a,bc', then splits the string back up into an array with the desired result, as you can see in the output.
This can be fool-proofed a bit, by changing the separator used in the join / split:
var test = ['a', 'b-', 'c'],
separator = '||',
result = test.join(separator)
.replace(new RegExp('-' + separator, 'g'), '')
.split(separator);
One possible approach (with .reduce):
var arr = ['a', 'b-', 'c'];
var trans = arr.reduce(function(acc, cur) {
if (acc.length && acc[acc.length - 1].slice(-1) === '-') {
acc[acc.length - 1] += cur;
}
else {
acc.push(cur);
}
return acc;
}, []);
This can also be achieved using Array.prototype.map:
var test = ['a', 'b-', 'c'];
var result = test.slice().map(function (x, i, a) {
if (x.endsWith("-") && a[i+1]) {
var r = x + a[i+1]; // Join this and the next element in the array
a.splice(i, 1); // Remove the next element from the array
return r;
}
return x;
}).filter(function (x) {
return typeof x !== 'undefined';
}); // Since the array comes back with a different length and some undefined elements, remove those. Thanks #Cerbrus for pointing this out
console.log(test, result, result.length); // ["a", "b-", "c"] ["a", "b-c"] 2
This way will work for multiple dashed elements in a row, and if the last element has a dash, uses Array.forEach
var test = ['a', 'b-', 'c-'], result = [], next = "";
test.forEach(function(curr) {
if (curr.endsWith('-')) {
next += curr;
if (curr == test[test.length-1]) {
result.push(next);
}
}else {
result.push(next + curr);
next = "";
}
});
document.write(result);
Another map + filter one. Most likely slower, as filter add's another iteration through the array, but works as the original does (which is probably not what the OP wants when there are multiple -'s in a row).
var test = ['a', 'b-', 'c-', 'd', 'e'], result = [];
result = test
.map((curr, i, array) => (curr.endsWith('-') && array[i + 1] !== undefined) ? curr + array[i+1] : curr)
.filter((curr, i, arr) => (i>0 && arr[i-1].length > 1 && curr.length === 1) ? false : true)
document.write(result);
Didn't read all answers, so sry if I repeat sth. that has already been said.
Functional programming doesn't mean that there is always a predefined function that does exactly what you intend to; or a combination of some.
Sometimes it is better to write a simple short utility-function than abusing the ones that are already there.
How about some "problems" like multiple dashed-values next to each other, or at the end of the list? How do you want to handle these cases?
This would be my implementation:
function combineDashedStrings(arr){
var result = [], pending = ""
for (var i=0; i<arr.length; i++) {
var curr = pending + arr[i];
pending = curr.endsWith("-") && curr || ""; //multiple concats
//pending = !pending && curr.endsWith("-") && curr || ""; //single concat
pending || result.push(curr);
}
//pending && result.push(curr); //add last item if it contains a dash
return result
}
combineDashedStrings(['a', 'b-', 'c-', 'd', 'e-']);
feel free to switch the commented lines/options

For loop array confusion

Im working in a class and the built in editor is telling me this funtion returns "a", but i expected it to return "a","b","c","d". Can someone please explain what im not understanding here ?
function chunk(arr) {
for(var i = 0; i <= arr.length; i++)
return arr[i];
}
chunk(['a', 'b', 'c', 'd']);``
A function can only have one return value, so the code ends when the return is executed the first time. The return statement is defined to exit the function immediately.
The return value can however be an array (or an object), so if you want to return multiple values you can put them in an array:
function chunk(arr) {
var result = [];
for(var i = 0; i <= arr.length; i++) {
result.push(arr[i] + '!');
}
return result;
}
var values = chunk(['a', 'b', 'c', 'd']);
// values now contain ['a!', 'b!', 'c!', 'd!']
You are returning too early:
function chunk(arr) {
var newArr = [];
for(var i = 0; i <= arr.length; i++) {
newArr.push(arr[i]);
}
return newArr;
}
This should work.
It returns 'a' because the return statement itself is returning a specific item in the array
return arr[i];//in example above, i is 0
^^^
Imagine the array structured like
0 -> a
1 -> b
2 -> c
3 -> d
4 -> e
So, when you access with index 0, a is returned.
To return the whole array, do so without index
return arr;
You should use .map() method, which creates a new array with the results of calling a provided function on every element in this array.
It's more clean.
So just do :
function chunk(arr) {
//Map our array and return a new array
return arr.map(function(elm){
return elm;
});
};
var a = chunk(['a', 'b', 'c', 'd']);
console.log(a);
//Print ["a", "b", "c", "d"]

Categories

Resources