Problem
For a homework assignment, I was asked to complete this CodeStepByStep problem. It’s coded correctly and gave me a right answer. I’m just wondering if there’s an easier way to solve this problem without the big blocky code in the if
statement. The fact that I have to check each lowercase vowel AND THEN the uppercase vowel seems verbose.
Write a function named vowelCount that accepts a string and returns
the number of vowels (a, e, i, o, or u) that the string contains.For example, the call of
vowelCount("kookaburra")
should return 5 (two
o’s, 2 a’s, and one u). When passed a string without any vowels (such
as an empty string, “01234”, or “sky”), 0 should be returned.
function vowelCount(string) {
if(string.length == 0) { return 0; }
let count = 0;
for(let i = 0; i < string.length; i++) {
let v = string[i];
if(v == "a" || v == "e" || v == "i" || v == "o" || v == "u" ||
v == "A" || v == "E" || v == "I" || v == "O" || v == "U") {
count += 1;
}
}
return count;
}
Solution
An alternative route is to use string.replace()
and Regular Expressions to strip everything but the vowels from the string. Then count the length of the resulting string. This avoids iteration altogether.
const vowelCount = s => s.replace(/[^aeiou]/gi, '').length
console.log(vowelCount('The quick brown fox jumps over the lazy dog'))
The regex is /[^aeiou]/gi
which means match everything that’s NOT (^
) in the set (aeiou
), matching globally (g
flag) and without regard to case (i
flag). string.replace()
then uses this pattern to replace all matching characters with a blank string. What remains are your vowels.
I’m just wondering if there’s an easier way to solve this problem without the big blocky code in the
if
statement.
Well, you could put all of those in an array:
const vowels = ['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u'];
And then the if
condition can be simplified using Array.prototype.includes()
:
if( vowels.includes(v)) {
The fact that I have to check each lowercase vowel AND THEN the uppercase vowel seems verbose.
You could also include either the uppercase or lowercase letters, and then call String.prototype.toUpperCase()
or String.prototype.toLowerCase()
, though if performance is your goal, then the extra function call might be something to consider.
Additionally, a for...of
loop could be used instead of the regular for
loop, to avoid the need to index into the array.
for( const v of string) {
And the postfix increment operator (i.e. ++
) could be used to increase count
instead of adding 1.
The check for zero length string can be removed, since the loop won’t be run.
const vowels = ['A', 'E', 'I', 'O', 'U'];
function vowelCount(string) {
let count = 0;
for( const v of string) {
if( vowels.includes(v.toUpperCase())) {
count++;
}
}
return count;
}
console.log(vowelCount('kookaburra'));
console.log(vowelCount('sky'));
What follows are some advanced techniques that many wouldn’t expect a beginner/intermediate-level student to utilize. If you really wanted to shorten this code, you could convert the string to an array with the spread operator and then use array reduction with Array.prototype.reduce()
:
const vowels = ['A', 'E', 'I', 'O', 'U'];
const charInVowels = c => vowels.includes(c.toUpperCase());
const checkChar = (count, c) => charInVowels(c) ? ++count : count;
const vowelCount = string => [...string].reduce(checkChar, 0);
console.log(vowelCount('kookaburra'));
console.log(vowelCount('sky'));
—-
P.s.did you intentionally put an auto-comment about off-topic posts as the body of your profile??
Sᴀᴍ Onᴇᴌᴀ answer has the right idea for small strings, but can be improved by using a Set to hold the vowels rather than an array. This reduces the overhead of Array.includes
which will iterate each character in the vowels array for non matching characters
You can create a set as const vowels = new Set([..."AEIOUaeiou"]);
To encapsulate the constant vowels
use a function to scope vowels
outside the global scope and closure to make it available to the function.
const countVowels = (() => {
const VOWELS = new Set([..."AEIOUaeiou"]);
return function(str) {
var count = 0;
for (const c of str) { count += VOWELS.has(c) }
return count;
}
})();
Or
const countVowels = (() => {
const VOWELS = new Set([..."AEIOUaeiou"]);
return str => [...str].reduce((count, c) => count += VOWELS.has(c), 0);
})();
UNICODE vowels
Of course the first snippet is the better as it is O(1) storage and as it uses a Set
(hash table) it is O(n) complexity (where n is the length of the string) rather than O(n∗m) (where m is the number of vowels). This becomes more important if you are to include the full set of unicode vowels
const countVowels = (() => {
// Reference https://en.wikipedia.org/wiki/Phonetic_symbols_in_Unicode
const VOWELS = new Set([..."AEIOUaeiouiyɨʉiyɯuɪʏeøɘɵɤoɛœɜɞʌɔaɶɑɒʊəɐæɪ̈ʊ̈IYƗɄIYƜUꞮʏEØɘƟɤOƐŒꞫɞɅƆAɶⱭⱰƱƏⱯÆꞮ̈Ʊ̈"]);
return function(str) {
var count = 0;
for (const c of str) { count += VOWELS.has(c) }
return count;
}
})();
NOTE the above snippet does not work.. see below.
Don’t split unicode
If you are using Unicode it is important to release that each unicode 16 bit character does not always represent a single visual character
For example the last two vowels “ɪ̈ʊ̈” require two characters to display. eg the string "u026Au0308u028Au0308" === "ɪ̈ʊ̈"
is true. You can not just count them
as in the expression "ɪ̈ʊ̈".split("").length
will evaluate to 4.
This is even more problematic as u026A
and u028A
the first character codes are also vowels "ɪʊ"
To solve for the full set of vowels and keep the complexity at O(n) and storage at O(1) we can use 3 sets
const countVowels = (() => {
const VOWELS = new Set([ ..."AEIOUaeiouiyɨʉiyɯuʏeøɘɵɤoɛœɜɞʌɔaɶɑɒəɐæIYƗɄIYƜUʏEØɘƟɤOƐŒꞫɞɅƆAɶⱭⱰƏⱯÆ"]);
const VOWELS_DOUBLE = new Set(["ɪ̈", "ʊ̈", "Ɪ̈", "Ʊ̈"]);
const VOWELS_SINGLE = new Set([..."ʊɪƱꞮ"]);
return function(str) {
var count = 0, prev;
for (const c of str) {
count += VOWELS.has(c);
count += VOWELS_DOUBLE.has(prev + c) || VOWELS_SINGLE.has(prev);
prev = c;
}
return count + VOWELS_SINGLE.has(prev);
}
})();
Maybe there is a risk that the very same authority which is asking you to count vowels will soon ask you to count consonants, just to have you see how flexible your code is.
So it could be a good idea to start with a function that takes two arguments:
- the string under test and
- the set of accepted vowels.
Like function countCharsFromVowelSet()
in the below code snippet.
Note that deciding what exactly is an acceptable vowel is a language and country dependent decision.
const countCharsFromVowelSet = function(str, vowelSet) {
let arr = [...str];
let count = (arr.filter(c => vowelSet.includes(c))).length;
return count;
};
/* auxiliary function builder function: */
const makeCharCounter = function(charSet) {
return (str => countCharsFromVowelSet(str, charSet));
};
const EnglishVowelList = "AEIOUaeiou";
const GermanVowelList = "AEIOUYÄÖÜaeiouyäöü";
const countEnglishVowels = makeCharCounter(EnglishVowelList);
const countGermanVowels = makeCharCounter(GermanVowelList);
text1 = "William Shakespeare";
count1 = countEnglishVowels(text1);
text2 = "Die Schöpfung";
count2 = countGermanVowels(text2);
console.log("There are " + count1.toString() + " vowels in: " + text1);
console.log("There are " + count2.toString() + " vowels in: " + text2);
function countVowels(str){
let vowel_count = 0;
str = str.toLowerCase();
for (let i = 0; i < str.length; i++) {
if (str.charAt(i) === 'a' || str.charAt(i) === 'e' || str.charAt(i) === 'i'
|| str.charAt(i) === 'o' || str.charAt(i) === 'u'){
vowel_count++;
}
}
return vowel_count;
}
console.log(countVowels("Celebration"))