Letter Count I JavaScript Challenge on Coderbyte - javascript

I've been on this problem for several hours now and have done all I can to the best of my current newbie javaScript ability to solve this challenge but I just can't figure out exactly what's wrong. I keep getting "UNEXPECTED TOKEN ILLEGAL on here: http://jsfiddle.net/6n8apjze/14/
and "TypeError: Cannot read property 'length' of null": http://goo.gl/LIz89F
I think the problem is the howManyRepeat variable. I don't understand why I'm getting it can't read the length of null when clearly word is a word from str...
I got the idea for:
word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length
...here: Get duplicate characters count in a string
The Challenge:
Using the JavaScript language, have the function LetterCountI(str) take the str
parameter being passed and return the first word with the greatest number of
repeated letters. For example: "Today, is the greatest day ever!" should return
greatest because it has 2 e's (and 2 t's) and it comes before ever which also
has 2 e's. If there are no words with repeating letters return -1. Words will
be separated by spaces.
function LetterCountI(str){
var wordsAndAmount={};
var mostRepeatLetters="-1";
var words=str.split(" ");
words.forEach(function(word){
// returns value of how many repeated letters in word.
var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/([.])\1+/g).length;
// if there are repeats(at least one value).
if(howManyRepeat !== null || howManyRepeat !== 0){
wordsAndAmount[word] = howManyRepeat;
}else{
// if no words have repeats will return -1 after for in loop.
wordsAndAmount[word] = -1;
}
});
// word is the key, wordsAndAmount[word] is the value of word.
for(var word in wordsAndAmount){
// if two words have same # of repeats pick the one before it.
if(wordsAndAmount[word]===mostRepeatLetters){
mostRepeatLetters=mostRepeatLetters;
}else if(wordsAndAmount[word]<mostRepeatLetters){
mostRepeatLetters=mostRepeatLetters;
}else if(wordsAndAmount[word]>mostRepeatLetters){
mostRepeatLetters=word;
}
}
return mostRepeatLetters;
}
// TESTS
console.log("-----");
console.log(LetterCountI("Today, is the greatest day ever!"));
console.log(LetterCountI("Hello apple pie"));
console.log(LetterCountI("No words"));
Any guidance is much appreciated. Thank you!! ^____^

Here is the working code snippet:
/*
Using the JavaScript language, have the function LetterCountI(str) take the str
parameter being passed and return the first word with the greatest number of
repeated letters. For example: "Today, is the greatest day ever!" should return
greatest because it has 2 e's (and 2 t's) and it comes before ever which also
has 2 e's. If there are no words with repeating letters return -1. Words will
be separated by spaces.
console.log(LetterCountI("Today, is the greatest day ever!") === "greatest");
console.log(LetterCountI("Hello apple pie") === "Hello");
console.log(LetterCountI("No words") === -1);
Tips:
This is an interesting problem. What we can do is turn the string to lower case using String.toLowerCase, and then split on "", so we get an array of characters.
We will then sort it with Array.sort. After it has been sorted, we will join it using Array.join. We can then make use of the regex /(.)\1+/g which essentially means match a letter and subsequent letters if it's the same.
When we use String.match with the stated regex, we will get an Array, whose length is the answer. Also used some try...catch to return 0 in case match returns null and results in TypeError.
/(.)\1+/g with the match method will return a value of letters that appear one after the other. Without sort(), this wouldn't work.
*/
function LetterCountI(str){
var wordsAndAmount={};
var mostRepeatLetters="";
var words=str.split(" ");
words.forEach(function(word){
var howManyRepeat=word.toLowerCase().split("").sort().join("").match(/(.)\1+/g);
if(howManyRepeat !== null && howManyRepeat !== 0){ // if there are repeats(at least one value)..
wordsAndAmount[word] = howManyRepeat;
} else{
wordsAndAmount[word] = -1; // if no words have repeats will return -1 after for in loop.
}
});
// console.log(wordsAndAmount);
for(var word in wordsAndAmount){ // word is the key, wordsAndAmount[word] is the value of word.
// console.log("Key = " + word);
// console.log("val = " + wordsAndAmount[word]);
if(wordsAndAmount[word].length>mostRepeatLetters.length){ //if two words have same # of repeats pick the one before it.
mostRepeatLetters=word;
}
}
return mostRepeatLetters ? mostRepeatLetters : -1;
}
// TESTS
console.log("-----");
console.log(LetterCountI("Today, is the greatest day ever!"));
console.log(LetterCountI("Hello apple pie"));
console.log(LetterCountI("No words"));
/*
split into words
var wordsAndAmount={};
var mostRepeatLetters=0;
loop through words
Check if words has repeated letters, if so
Push amount into object
Like wordsAndAmount[word[i]]= a number
If no repeated letters...no else.
Loop through objects
Compare new words amount of repeated letters with mostRepeatLetters replacing whoever has more.
In the end return the result of the word having most repeated letters
If all words have no repeated letters return -1, ie.
*/
The changes made:
[.] turned into . as [.] matches a literal period symbol, not any character but a newline
added closing */ at the end of the code (the last comment block was not closed resulting in UNEXPECTED TOKEN ILLEGAL)
if(howManyRepeat !== null || howManyRepeat !== 0) should be replaced with if(howManyRepeat !== null && howManyRepeat !== 0) since otherwise the null was testing for equality with 0 and led to the TypeError: Cannot read property 'length' of null" issue. Note that .match(/(.)\1+/g).length cannot be used since the result of matching can be null, and this will also cause the TypeError to appear.
The algorithm for getting the first entry with the greatest number of repetitions was wrong since the first if block allowed subsequent entry to be output as a correct result (not the first, but the last entry with the same repetitions was output actually)
-1 can be returned if mostRepeatLetters is empty.

Hope you dont mind if I rewrite this code. My code may not be that efficient.
Here is a snippet
function findGreatest() {
// ipField is input field
var getString = document.getElementById('ipField').value.toLowerCase();
var finalArray = [];
var strArray = [];
var tempArray = [];
strArray = (getString.split(" "));
// Take only those words which has repeated letter
for (var i = 0, j = strArray.length; i < j; i++) {
if ((/([a-zA-Z]).*?\1/).test(strArray[i])) {
tempArray.push(strArray[i]);
}
}
if (tempArray.length == 0) { // If no word with repeated Character
console.log('No such Word');
return -1;
} else { // If array has words with repeated character
for (var x = 0, y = tempArray.length; x < y; x++) {
var m = findRepWord(tempArray[x]); // Find number of repeated character in it
finalArray.push({
name: tempArray[x],
repeat: m
})
}
// Sort this array to get word with largest repeated chars
finalArray.sort(function(z, a) {
return a.repeat - z.repeat
})
document.getElementById('repWord').textContent=finalArray[0].name;
}
}
// Function to find the word which as highest repeated character(s)
function findRepWord(str) {
try {
return str.match(/(.)\1+/g).length;
} catch (e) {
return 0;
} // if TypeError
}
Here is DEMO

function LetterCountI(str) {
var word_arr = str.split(" ");
var x = word_arr.slice();
for(var i = 0; i < x.length; i ++){
var sum = 0;
for(var y = 0; y < x[i].length; y++){
var amount = x[i].split("").filter(function(a){return a == x[i][y]}).length;
if (amount > 1){
sum += amount
}
}
x[i] = sum;
}
var max = Math.max.apply(Math,x);
if(max == 0)
return -1;
var index = x.indexOf(max);
return(word_arr[index]);
};
Here is another version as well.

You could use new Set in the following manner:
const letterCount = s => {
const res = s.split(' ')
.map(s => [s, (s.length - new Set([...s]).size)])
.reduce((p, c) => (!p.length) ? c
: (c[1] > p[1]) ? c : p, []);
return !res[1] ? -1 : res.slice(0,1).toString()
}
Note: I have not tested this solution (other than the phrases presented here), but the idea is to subtract unique characters from the total characters in each word of the phrase.

Related

Find if the sentence has 3 consecutive words

You are given a string with words and numbers separated by whitespaces (one space). The words contains only letters. You should check if the string contains three words in succession. For example, the string "start 5 one two three 7 end" contains three words in succession.
Input : String
Output : Boolean
This is what I'm trying to do, please point out my mistake. Thanks.
function threeWords(text){
let lst = text.split(' ');
for (let i=0; i< 3; i++) {
if (typeof lst[i] === 'string' && Number(lst[i]) === NaN) {return true}
else {return false;}}
}
If you'd rather continue with your code than use regex, here are your issues:
You only loop over 3 of the elements in lst. Loop over the entire length of the list.
You try to check if Number('somestring') === NaN. In JavaScript, NaN === NaN is False. Use isNaN() instead.
Once you find a list element that is not a number, you return True. You should have a variable that keeps track of how many words there are in succession (resetting to 0 when you find a number), and return True when this variable is equal to 3.
Here is the fixed code:
function threeWords(text) {
let lst = text.split(' ');
let num_words = 0;
for (let i = 0; i < lst.length; i++) {
if (isNaN(Number(lst[i])))
num_words++
else
num_words = 0
if (num_words === 3)
return true
}
return false;
}
Might be easier with a regular expression:
const result = /([A-Za-z]+( |$)){3}/.test('start 5 one two three 7 end');
console.log(result);

Understanding solution of Valid Palindrome in Javascript

This is from LeetCode - Valid Palindrome.
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
Note: For the purpose of this problem, we define empty string as valid palindrome.
while (regex.test(s[start])) {
start++;}
--> can't understand how it works, I understood only that s[start] is alphanumeric characters, it will be false
if (!s[start] || !s[end])
--> What does this mean?
Below is the whole code
var isPalindrome = function(s) {
let regex = /[\W]/;
let start = 0;
let end = s.length - 1;
while (start < end) {
// Moves front runner to next alphanumeric
while (regex.test(s[start])) {
start++;
}
// Moves back runner to next alphanumeric
while (regex.test(s[end])) {
end--;
}
// Above would run until null if there are no alphanumeric characters so return true
if (!s[start] || !s[end]) {
return true;
}
// Check if equal and return false if not
if (s[start].toLowerCase() != s[end].toLowerCase()) {
return false;
}
// If index values match continue the while loop
start++;
end--;
}
return true;
};
Please Advise!
Same as previous solution, plus:
+ removing spaces and symbols, except (A-Z,a-z,0-9)
+ lowercasing
const isPalindrome = function(str) {
const expr = /[\W_]/g;
const lowcaseStr = str.toLowerCase().replace(expr, '');
const reverseStr = lowcaseStr.split('').reverse().join('');
return lowcaseStr === reverseStr
};
This is heavily over-engineered. Why not just use one for loop with 2 counters, one starting at 0 and one at the last index and, while they aren't equal, check if the characters at those indices are the same. Or use built in functions like
function palindrome(str){ return str.split('').reverse().join('') == str}

Count characters in strings with or without string.slice

I am trying to learn how this problem has been solved but I am lost at string.slice(0,1) and i++ after that.
What is the need to use the slice method?
The questions is:
Write a function called countChars that accepts two parameters: a string and a character. This function should return a number representing the number of times that the character appears in string.
function countChars(string, character) {
let count = 0;
let i = 0;
while (i < string.length) {
if (string[i] === character) {
count++;
}
string.slice(0, 1);
i++;
}
return count;
}
Nina already solved it using your code, a shorter option would be using String.match
function countChars(string, character) {
const { length } = string.match(new RegExp(character, 'ig')) || [];
return length;
}
console.log(countChars('Foobar', 'o'));
Explanation of your code:
function countChars(string, character) {
let count = 0; // set variable count to zero
let i = 0; // set variable i to zero
while (i < string.length) { // loop while i is less than lenght of string
if (string[i] === character) { // check character at position i
count++; // Increment count by one
}
string.slice(0, 1); // returns the first character in string, but return value is not used. Can be removed
i++; // increment variable i by one
}
return count; // returns count
}
console.log("Count", countChars("ABCDABC", "A"));
string.slice(0, 1) The slice method extracts a section of a string and returns it as a new string, without modifying the original string. The return value is not used in this function, so you can remove the call. I guess that the person who wrote it tried to remove the first character of the string for each iteration of the loop. That is not a good way to solve the problem, for example because it creates one string per iteration and wastes memory.
i++ The increment operator ++ increments (adds one to) its operand and returns a value. There are two ways to use this operator: as pre increment ++i (returns the value before incrementing) or post increment i++ (returns the value after incrementing). Both variants are useful. When used on a single line (like in your example) it doesn't matter. You can also write it as i = i + 1 or using the addition assignment i += 1.
let value = 0;
console.log( 'Pre increment: before %d, result %d, after %d', value, ++value, value); // before 0, result 1, after 1
value = 0;
console.log( 'Post increment: before %d, result %d, after %d', value, value++, value); //before 0, result 0, after 1
Using the while-statement is one way to create the loop that iterates over the string, using the for-statement is another, more compact way.
Example with while:
let i = 0; // initialization
while ( i < string.length ) { // check contition
// ... some code ...
i += 1;
}
Example with for:
for ( let i = 0; i < string.length; i += 1 ) {
// ... some code ...
}
The pros with for are: the initialization, condition and final expression is defined in the header. The scope of the i variable can with let be restricted to be defined only inside the for-loop.
Your example, but with a for-loop:
function countChars(string, character) {
let count = 0; // set variable count to zero
for (let i = 0; i < string.length; i += 1) {
if (string[i] === character) { // check character at position i
count++; // Increment count by one
}
}
return count; // returns count
}
console.log("Count", countChars("ABCDABC", "A"));
The code is buggy!
There is a bug with this solution, and it has to do with how characters are defined. In Javascript each position of the string is a 16-bit integer. It works for most text, but fails for emojis and other characters that aren't in the Unicode Basic Multilingual Plane since they are defined with two positions in the string (surrogate pairs).
A solution that works is to use the split-method, that splits a string object into an array of strings, using a specified separator string to determine where to make each split. Note that if the separator string isn't found, or if the input string is empty, an array of one string will be returned. If there is one match, the returned array will have two strings. So the split-method returns a value that is one more than what we want, but that is easy to fix by just subtracting one.
function countChars(string, character) {
return string.split(character).length - 1;
}
console.log("Count", countChars("ABCD😃ABC😃", "😃"));
Optimizations
While “Premature optimization is the root of all evil”, there are some things that can be written in a clean and optimized way while you write the code. For example, if you are writing to a file, it is very inefficient to open the file, write something, and then close the file inside the loop:
for( i = 0; i < array.length; i + i = 1) {
fs.openFile("log.txt").write(array[i]).close();
}
It should be no surprise that it is more efficient to first open the file, write inside the loop, and then close the file:
fh = fs.openFile("log.txt");
for( i = 0; i < array.length; i + i = 1) {
fh.write(array[i]);
}
fh.close();
The reason I mention this, is because when I write a for-statement, I usually initialize both the variable and the length in the initialization part of the for-statement, since the length of the string doesn't change (and using the property string.length is always more expensive than using a local variable).
for (let i = 0, length = string.length; i < length; i += 1) {
// ... code ...
}
Another thing: string[i] and string.charAt(i) creates a new string in every iteration of the loop. To compare a single character inside a loop, it is much faster to compare integers instead of strings, by using string.charCodeAt(i). And instead of string.charCodeAt(i) you can use string.codePointAt(i) to make it safe to use with all Unicode characters like emoji, not only the characters in the BMP.
As I said above, the method that you used isn't safe to use with Unicode. For example, if you search for an emoji (😃), you will not get the correct result.
The following two methods are safe to use with all Unicode codepoints. Both of them can handle a needle with multiple characters.
function count_split(haystack, needle) {
return haystack.split(needle).length - 1;
}
This function uses a regular expression as the needle, and that can give unexpected results, since some characters have special meaning. For example, if you search for a dot (.) it will match every character. On the other hand, you can do an advanced search for '[ab]' that will match all 'a' and 'b' characters.
function count_match(haystack, needle) {
const match = haystack.match(new RegExp(needle, 'g')) || [];
return match.length;
}
The following method is safe to use with any Unicode, and the needle must be a single codepoint.
function countChars(haystack, needle) {
let count = 0;
const codePoint = needle.codePointAt(0);
if (
!((needle.length === 1) || (needle.length === 2 && codePoint > 0xffff))
) {
// NOTE: Using charPointAt(0) returns the codePoint of the first character.
// Since the characters in the string is stored as 16-bit integers,
// characters outside of the Unicode BMP is stored as surrogate pairs
// in two positions. Thats why the length of the string is1 for
// a BMP codepoint and 2 for a codepoint outside of the BMP.
// codePointAt handles the decoding of the surrogate pair.
throw Error('needle should be one codepoint');
}
for (let i = 0, length = haystack.length; i < length; i += 1) {
if (haystack.codePointAt(i) === codePoint) {
count++;
}
}
return count;
}
I have created test-cases on jsperf.com to compare the speed of the Unicode safe-functions mentioned above, and also the UNSAFE-functions that where published in this thread while I was writing this answer.
Without slice
function countChars(string, character) {
let count = 0;
let i = 0;
while (i < string.length) {
if (string[i] === character) {
count++;
}
i++;
}
return count;
}
console.log(countChars('foobar', 'o'))
With slice, but without i.
function countChars(string, character) {
let count = 0;
while (string.length) { // check if the length is not zero
if (string[0] === character) {
count++;
}
string = string.slice(1); // take the rest of the string, beginning from index 1
}
return count;
}
console.log(countChars('foobar', 'o'))
If you're willing to look for other solutions, here is a short one.
function count(str, char){
let count = str.split('').filter(c => {
return c.toLowerCase() === char.toLowerCase()
});
return count.length;
}
console.log(count('hello world', 'l'));
Note: The above solution is case-insensitive.
I would do it like this
function countChars(string, character) {
return string.split('').filter(e => e === character).length;
}
console.log(countChars('foobar', 'o'))
But #bambam RegEx approach is likely the most efficient.
A shorter version :)
function count(string,char) {
return string.split('').filter(x => x === char).length;
}
Thanks

How to get a term before a character?

How to get the number before 'x'?
I tried using .split('x')[0] but it grabs everything before 'x'.
123x // Gives 123
123y+123x - // Gives 123
123x+123x - // Gives 246
I've tested a function which uses regex that I think will work. I've included the results, an explanation of how the function works, and then a commented version of the function.
Note, this doesn't handle any algebra more complex than adding and subtracting simple terms. I would refer to https://newton.now.sh/ for that, it's an API which can handle simplification (I am not affiliated).
Results:
console.log(coefficient("-x+23x")); // 22
console.log(coefficient("123y+123x")); // 123
// replaces spaces
console.log(coefficient("x + 123x")); // 124
console.log(coefficient("-x - 123x")); // -124
console.log(coefficient("1234x-23x")); // 1211
// doesn't account for other letters
console.log(coefficient("-23yx")); // 1
Explanation:
First the function removes spaces. Then it uses a regex, which finds any sequence of numbers that are followed by an 'x'. If there's a +/- in front, the regex keeps that. The function loops through these sequences of numbers, and adds them to a total. If there's an 'x' that does not have numbers with it, its coefficient is assumed as -1 or 1.
Commented Code:
function coefficient(str) {
// remove spaces
str = str.replace(/\s/g, '');
// all powerful regex
var regexp = /(\+|-)?[0-9]*x/g
// total
sum = 0;
// find the occurrences of x
var found = true;
while (found) {
match = regexp.exec(str);
if (match == null) {
found = false;
} else {
// treated as +/- 1 if no proceeding number
if (isNaN(parseInt(match[0]))) {
if (match[0].charAt(0) == "-") {
sum--;
} else {
sum++;
}
// parse the proceeding number
} else {
sum += parseInt(match[0]);
}
}
}
return sum;
}
I don't know if there is sufficient cleverness in ECMAScript regular expressions to do look behind, but you can do it with match and post processing to remove the "x".
If the intention is to sum the terms, then a further operation with reduce is required. Trimming the x could be combined with reduce so that map isn't required.
console.log(
'123x+123x'.match(/\d+x/ig).map(function(v){
return v.slice(0,-1)
}).reduce(function(sum, v){return sum + +v},0)
);
console.log(match('123x+123y+456x', 'x'))
console.log(match('123x+123y+456x', 'y'))
function match(str, x) {
return str.match(new RegExp('\\d+' + x, 'g')).reduce((cur, p) => {
return cur + parseInt(p.substr(0, p.length - x.length))
}, 0)
}

counting a word and returning a whether it is symmetric or not in Javascript

My whole goal was to write a loop that would take a string, count the letters and return two responses: one = "this word is symmetric" or two = "this word is not symmetric". However the code I wrote doesn't console anything out. Here's the code:
var arya = function(arraycount){
for (arraycount.length >= 1; arraycount.length <= 100; arraycount++) {
while (arraycount.length%2 === 0) {
console.log("This is a symmetric word and its length is " + " " arraycount.length " units.");
arraycount.length%2 != 0
console.log("Not a symmetric word");
}
}
}
arya("Michael");
There are many ways to accomplish your goal, but here are a few. The first is a somewhat naïve approach using a for loop, and the second uses recursion. The third asks whether the string equals the reverse of the string.
iterative (for loop) function
var isPalindromeIteratively = function(string) {
if (string.length <= 1) {
return true;
}
for (var i = 0; i <= Math.floor(string.length / 2); i++) {
if (string[i] !== string[string.length - 1 - i]) {
return false;
}
}
return true;
};
This function begins by asking whether your input string is a single character or empty string, in which case the string would be a trivial palindrome. Then, the for loop is set up: starting from 0 (the first character of the string) and going to the middle character, the loop asks whether a given character is identical to its partner on the other end of the string. If the parter character is not identical, the function returns false. If the for loop finishes, that means every character has an identical partner, so the function returns true.
recursive function
var isPalindromeRecursively = function(string) {
if (string.length <= 1) {
console.log('<= 1');
return true;
}
var firstChar = string[0];
var lastChar = string[string.length - 1];
var substring = string.substring(1, string.length - 1);
console.log('first character: ' + firstChar);
console.log('last character: ' + lastChar);
console.log('substring: ' + substring);
return (firstChar === lastChar) ? isPalindromeRecursively(substring) : false;
};
This function begins the same way as the first, by getting the trivial case out of the way. Then, it tests whether the first character of the string is equal to the last character. Using the ternary operator, the function, returns false if that test fails. If the test is true, the function calls itself again on a substring, and everything starts all over again. This substring is the original string without the first and last characters.
'reflecting' the string
var reflectivePalindrome = function(string) {
return string === string.split('').reverse().join('');
};
This one just reverses the string and sees if it equals the input string. It relies on the reverse() method of Array, and although it's the most expressive and compact way of doing it, it's probably not the most efficient.
usage
These will return true or false, telling you whether string is a palindrome. I assumed that is what you mean when you say "symmetric." I included some debugging statements so you can trace this recursive function as it works.
The Mozilla Developer Network offers a comprehensive guide of the JavaScript language. Also, here are links to the way for loops and while loops work in JS.

Categories

Resources