JS - why does this Codewars challenge return undefined for one test while 104 other tests pass? - javascript

I'm solving the Highest Scoring Word challenge on Codewars, which states -
Given a string of words (x), you need to find the highest scoring
word.
Each letter of a word scores points according to it's position in the
alphabet. a=1, z=26 and everything inbetween.
You need to return the highest scoring word as a string.
If two words score the same, return the word that appears earliest in
the original string.
All letters will be lower case and all inputs will be valid.
My approach to the problem is as follows -
Construct an object, that maps alphabets to their corresponding integer values, like a - 1, b - 2, etc.
Split the input string on spaces, for each word - find it's score by-
creating an array of the word's letters
map over the array to get score of each letter
reduce this array with addition and get the total score of the word
The array returned in step 2 will have scores for each word in the original string. Find the maximum value in this array and get it's position, return the word from the original string at that particular position.
My code snippet (demo here ) -
function high(x) {
let myObj = {};
for (let i = 1; i <= 26; i++) {
myObj[String.fromCharCode(i + 96)] = i;
}
// console.log(myObj);
let scores = x.split(' ').map(word => [...word].map(a => myObj[a]).reduce((a, b) => a + b, 0));
return x.split(' ')[scores.indexOf(Math.max(...scores))];
}
console.log(high('take me to semynak'))
When I run this in codewars, the results show 104 passed tests, 1 failed, and 1 error. The only info. about the failures/errors shown is -
Expected: 'bintang', instead got: undefined
Since the actual input for this test case is not shown, this info. by itself is not very useful.
I tried to think of some edge cases, like two words having the same score, but even then .indexOf() should return the position of the earlier value, as you can see in this example -
let nums = [1, 2, 3, 3];
console.log(nums.indexOf(Math.max(...nums)));
Here's the screenshot -

The statement of the problem says that the words will consist of lowercase letters only, but it doesn't guarantee that the input string will contain only words and spaces.
In order to account for punctuation, numbers and other non-words, you need to extract all sequences of lowercase letters as words (instead of splitting the input string on spaces only).
function high(x) {
let words = x.split(/[^a-z]+/);
let scores = words.map(word => [...word].map(a => a.charCodeAt(0) - 96).reduce((a, b) => a + b, 0));
return words[scores.indexOf(Math.max(...scores))];
}
console.log(high('today is 24 august, 2017'));

Related

JS Regex returning -1 & 0

I was tasked with the following:
take a string
print each of the vowels on a new line (in order) then...
print each of the consonants on a new line (in order)
The problem I found was with the regex. I originally used...
/[aeiouAEIOU\s]/g
But this would return 0 with a vowel and -1 with a consonant (so everything happened in reverse).
I really struggled to understand why and couldn't for the life of me find the answer. In the end it was simple enough to just invert the string but I want to know why this is happening the way it is. Can anyone help?
let i;
let vowels = /[^aeiouAEIOU\s]/g;
let array = [];
function vowelsAndConsonants(s) {
for(i=0;i<s.length;i++){
//if char is vowel then push to array
if(s[i].search(vowels)){
array.push(s[i]);
}
}
for(i=0;i<s.length;i++){
//if char is cons then push to array
if(!s[i].search(vowels)){
array.push(s[i]);
}
}
for(i=0;i<s.length;i++){
console.log(array[i]);
}
}
vowelsAndConsonants("javascript");
if(vowels.test(s[i])){ which will return true or false if it matches, or
if(s[i].search(vowels) !== -1){ and if(s[i].search(vowels) === -1){
is what you want if you want to fix your code.
-1 is not falsey so your if statement will not function correctly. -1 is what search returns if it doesn't find a match. It has to do this because search() returns the index position of the match, and the index could be anywhere from 0 to Infinity, so only negative numbers are available to indicate non-existent index:
MDN search() reference
Below is a RegEx that matches vowel OR any letter OR other, effectively separating out vowel, consonant, everything else into 3 capture groups. This makes it so you don't need to test character by character and separate them out manually.
Then iterates and pushes them into their respective arrays with a for-of loop.
const consonants = [], vowels = [], other = [];
const str = ";bat cat set rat. let ut cut mut,";
for(const [,cons,vow,etc] of str.matchAll(/([aeiouAEIOU])|([a-zA-Z])|(.)/g))
cons&&consonants.push(cons) || vow&&vowels.push(vow) || typeof etc === 'string'&&other.push(etc)
console.log(
consonants.join('') + '\n' + vowels.join('') + '\n' + other.join('')
)
There are a couple of inbuilt functions available:
let some_string = 'Mary had a little lamb';
let vowels = [...some_string.match(/[aeiouAEIOU\s]/g)];
let consonents = [...some_string.match(/[^aeiouAEIOU\s]/g)];
console.log(vowels);
console.log(consonents);
I think that you don't understand correctly how your regular expression works. In the brackets you have only defined a set of characters you want to match /[^aeiouAEIOU\s]/g and further by using the caret [^]as first in your group, you say that you want it to match everything but the characters in the carets. Sadly you don't provide an example of input and expected output, so I am only guessing, but I thing you could do the following:
let s = "avndexleops";
let keep_vowels = s.replace(/[^aeiouAEIOU\s]/g, '');
console.log(keep_vowels);
let keep_consonants = s.replace(/[aeiouAEIOU\s]/g, '');
console.log(keep_consonants);
Please provide example of expected input and output.
You used:
/[^aeiouAEIOU\s]/g
Instead of:
/[aeiouAEIOU\s]/g
^ means "not", so your REGEX /[^aeiouAEIOU\s]/g counts all the consonants.

How can I check a word is made of letters in different array

I am writing a word game in Vue.
You start with a string of random characters and you have to input a word that can be made from that string, eg for "ABEOHSTD" the user can enter "BASE" for a score of 4.
I have an external word list in a .txt file (which I also can't get working, but that's a separate issue) I need to verify the words against, but I'm having trouble verifying the words can be made from the given random string.
I don't know how I'd approach making sure each letter can only be used as many times as it appears in the array, or even store the scores but I just want to get this first part working.
I have tried splitting both the entered word and random string into an array of each character, looping through the array and checking if each character in the user entered string is included in the random string array.
splitUserCurrentWord = this.userAttemptedWord.split("");
for (var i = 0; i <= splitUserCurrentWord.length; i++) {
if (this.randomStringForGame.split("").includes(splitUserCurrentWord[i])) {
return true;
//i++
}
else {
return false;
}
}
}
Currently I expect to resolve to true if all the letters in the user inputted word are present in the random string array, but it seems to only resolve to true or false based on the first letter of the array, which isn't good because as long as the first letter is in the random string array it will register as true/correct.
jsfiddle of the entire thing so far:
https://jsfiddle.net/sk4f9d8w/
Your return statement is exiting the loop after the 1st iteration.
One way to do it is to use Array.every to verify all the letters and String.includes to check if the letter is part of the accepted String
const randomString = "ABEOHSTD";
console.log(isWordValid(randomString, "BASE"));
console.log(isWordValid(randomString, "BASEU"));
function isWordValid(validLetters, attemtedWord) {
const attemptedWordSplitted = attemtedWord.split("");
return attemptedWordSplitted.every(attemptedLetter => validLetters.includes(attemptedLetter));
}
If you don't allow to reuse the same letter multiple times, you need another approach by deleting the used letter from the list of acceptable letters
const randomString = "ABEOHSTD";
console.log(isWordValid(randomString, "BASE"));
console.log(isWordValid(randomString, "BAASE"));
console.log(isWordValid(randomString, "BASEU"));
function isWordValid(validLetters, attemptedWord) {
const validLettersSplitted = validLetters.split("");
const attemptedWordSplitted = attemptedWord.split("");
return attemptedWordSplitted.every(attemptedLetter => {
const letterIndex = validLettersSplitted.indexOf(attemptedLetter);
if(letterIndex > -1){
validLettersSplitted.splice(letterIndex, 1);
return true;
} else {
return false
}
});
}
You are on the right way, You need to check each letter in the user word and check if they are in the random word. If one letter is in the random word, you remove it from the random word so it can't be use twice. :
let randomWord = "ABEOHSTD";
let userWordThatFail = "BAASE";
let userWord = "BASE";
// optionnaly, uppercase both words.
// we split into letters to make it easiers to process
let randomLetters = randomWord.split('');
let userLetters = userWord.split('');
let score = 0;
//we parse each letter of the user input
userLetters.forEach((letter) => {
// the letter exists in the random word.
let indexOfTheCurrentLetter = randomLetters.indexOf(letter);
// the letter exists, we remove it and increment the score.
if(indexOfTheCurrentLetter !== -1) {
randomLetters.splice(indexOfTheCurrentLetter, 1);
score++;
}
});
// the user input contains letters that are not in the random input.
if(score < userLetters.length) {
console.log('fail');
} else {
console.log('win : ' + score);
}
A simple approach might be to iterate the list of valid characters for every character encountered in the string to test. Using string.includes would fall into this bracket. Problematically, that's O(n_validchars * n_testchars) time-complexity for each comparison. This might not be desirable for longer strings.
The JavaScript Set object can help here.
With this higher-order function (that leans heavily on the iterable nature of a string), you can generate a reusable function for a set of valid characters:
function testFor(validChars) {
const charSet = new Set(validChars);
return testString =>
Array.prototype.every.call(testString, c => charSet.has(c));
}
// And to use it:
const testForABC = testFor("ABC"); //returns a function
console.log(testForABC("AABBCC")); //true
console.log(testForABC("abc")); //false
Now, because Set lookups are O(1), we're looking at O(n) complexity where n is the length of the string we're testing. Much better.

Splitting a string based on max character length, but keep words into account

So In my program I can receive strings of all kinds of lengths and send them on their way to get translated. If those strings are of a certain character length I receive an error, so I want to check & split those strings if necessary before that. BUT I can't just split the string in the middle of a word, the words themself also need to be intact & taken into account.
So for example:
let str = "this is an input example of one sentence that contains a bit of words and must be split"
let splitStringArr = [];
// If string is larger than X (for testing make it 20) characters
if(str.length > 20) {
// Split string sentence into smaller strings, keep words intact
//...
// example of result would be
// splitStringArr = ['this is an input', 'example of one sentence' 'that contains...', '...']
// instead of ['this is an input exa' 'mple of one senten' 'ce that contains...']
}
But I'm not sure how to split a sentence and still keep into account the sentence length.
Would a solution for this be to iterate over the string, add every word to it and check every time if it is over the maximum length, otherwise start a new array index, or are there better/existing methods for this?
You can use match and lookahead and word boundaries, |.+ to take care string at the end which are less then max length at the end
let str = "this is an input example of one sentence that contains a bit of words and must be split"
console.log(str.match(/\b[\w\s]{20,}?(?=\s)|.+$/g))
Here's an example using reduce.
const str = "this is an input example of one sentence that contains a bit of words and must be split";
// Split up the string and use `reduce`
// to iterate over it
const temp = str.split(' ').reduce((acc, c) => {
// Get the number of nested arrays
const currIndex = acc.length - 1;
// Join up the last array and get its length
const currLen = acc[currIndex].join(' ').length;
// If the length of that content and the new word
// in the iteration exceeds 20 chars push the new
// word to a new array
if (currLen + c.length > 20) {
acc.push([c]);
// otherwise add it to the existing array
} else {
acc[currIndex].push(c);
}
return acc;
}, [[]]);
// Join up all the nested arrays
const out = temp.map(arr => arr.join(' '));
console.log(out);
What you are looking for is lastIndexOf
In this example, maxOkayStringLength is the max length the string can be before causing an error.
myString.lastIndexOf(/\s/,maxOkayStringLength);
-- edit --
lastIndexOf doesn't take a regex argument, but there's another post on SO that has code to do this:
Is there a version of JavaScript's String.indexOf() that allows for regular expressions?
I would suggest:
1) split string by space symbol, so we get array of words
2) starting to create string again selecting words one by one...
3) if next word makes the string exceed the maximum length we start a new string with this word
Something like this:
const splitString = (str, lineLength) => {
const arr = ['']
str.split(' ').forEach(word => {
if (arr[arr.length - 1].length + word.length > lineLength) arr.push('')
arr[arr.length - 1] += (word + ' ')
})
return arr.map(v => v.trim())
}
const str = "this is an input example of one sentence that contains a bit of words and must be split"
console.log(splitString(str, 20))

changing lower to uppercase without built in functions of string

I have created the loop to search the array for the string length and after that I am sort of stuck to where to go to next after that because y of the others.
function uppp(string) {
var c = '';
This seems like a homework question, so I'm only going to give you hints!
First, look into the concept of ASCII codes:
https://www.w3schools.com/charsets/ref_html_ascii.asp
You'll notice that the character codes for a-z (lowercase) are 97-122.
Likewise, the character codes for A-Z (capitalized) are 65-90.
High-level solution
Iterate through your string, checking if each character is lowercase.
Notes: 'a'.charCodeAt(0) === 97 and String.fromCharCode('a'.charCodeAt(0) + 1); === 'b'
With these notes in mind, see if you can develop a solution.
Look at the array functions such as map(), join(), and indexOf(). How could two arrays, one of lowercase and one of uppercase letters, be combined with these methods to produce the desired outcome?
var s = 'String is COOL!',
result = '',
lower = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'],
upper = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
function makeUpper(x) {
let index = lower.indexOf(x);
return index > -1 ? upper[index] : x;
}
result = [].map.call(s, makeUpper).join('');
console.log(result);

How to parse two space-delimited numbers with thousand separator?

I need to parse string that contains two numbers that may be in three cases :
"646.60 25.10" => [646.60 25.10]
"1 395.86 13.50" => [1395.86, 13.50]
"13.50 1 783.69" => [13.50, 1 783.69]
In a simple case it's enough use 'number'.join(' ') but in the some cases there is thousand separator like in second and third ones.
So how could I parse there numbers for all cases?
EDIT: All numbers have a decimal separator in the last segment of a number.
var string1 = "646.60 25.10";// => [646.60 25.10]
var string2 = "1 395.86 13,50";// => [1395.86, 13,50]
var string3 = "13.50 1 783.69"; // => [13.50, 1 783.69]
function getArray(s) {
var index = s.indexOf(" ", s.indexOf("."));
return [s.substring(0,index), s.substring(index+1) ];
}
console.log(getArray(string1));
console.log(getArray(string2));
console.log(getArray(string3));
Assuming every number ends with a dot-digit-digit (in the comments you said they do), you can use that to target the right place to split with aregex.
That way, it is robust and general for any number of numbers (although you specified you have only two) and for any number of digits in the numbers, as long as it ends with the digit-dot-digit-digit:
str1 = "4 435.89 1 333 456.90 7.54";
function splitToNumbers(str){
arr = str.replace(/\s+/g,'').split(/(\d+.\d\d)/);
//now clear the empty strings however you like
arr.shift();
arr.pop();
arr = arr.join('&').split(/&+/);
return arr;
}
console.log(splitToNumbers(str1));
//4435.89,1333456.90,7.54

Categories

Resources