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

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.

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.

Generating acronyms using JavaScript

This code below with its function to generate acronyms was extracted from Stanford Uni's lecture notes. The code checks for every character and correctly handles strings that have leading, trailing, or multiple spaces, or even hyphens. However, I have difficulty understanding a line of code.
function acronym(str) {
let result = "";
let inWord = false;
for (let i = 0; i < str.length; i++) {
let ch = str.charAt(i);
if (isLetter(ch)) {
if (!inWord) result += ch;
inWord = true;
} else {
inWord = false;
}
}
return result;
}
function isLetter(ch) {
return ch.length === 1 &&
ALPHABET.indexOf(ch.toUpperCase()) !== -1;
}
As shown in the code above, I'm not quite sure how the "inWord" variable works. I'm not sure how it sets word boundaries that are indicated by sequences of nonletters. If you don't mind, can someone please enlighten me?
Your help is much appreciated. Thanks!
The code tries to make an acronym, i.e. take the first letter of every word to create to create a new word.
Translation of the loop:
If the current character is a letter check if boolean flag is false
If the boolean is false, add the character to the current acronym value
Set the boolean flag to true, so the other letters of the word will not be executed until a separator is found
Start from step 1 when a separator is found (non-alphabetic character).
So basically it just aggregates the first letters of every word into a new string.

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))

Remove all consonants in a string before a vowel then add a character

I want to remove all consonants in a string before the occurrence of a vowel and then replace it with an 'r'.
This means that 'scooby' will become 'rooby', 'xylographer' will become 'rographer' and so on. This is the algorithm I came up with:
1. Check if input type is not a string.
2. Use a variable(newStr) to hold lowercase conversion and splitted word.
3. Declare a variable(arrWord) to hold the length of the array.
4. Another variable called regex to check if a string starts with a consonant
5. Variable newArr holds the final result.
6. Search through the array, if the string does not start with a consonant
join it and return it.
7. Else keep looking for where the first vowel occurs in the word.
8. When found, remove all characters(consonants) before the vowel occurence
and replace them with an r.
9. Join the array together.
I have been able to come up with this:
const scoobyDoo = str => {
if(typeof str !== 'string'){
return 'This function accepts strings only';
}
let newStr = str.toLowerCase().split('');
let arrWord = newStr.length;
let regex = /[aeiou]/gi;
for (let i = 0; i < arrWord; i++){
if (newStr[0].match(regex)) {
let nothing = newStr.join('');
return nothing;
}
else {
let vowelIndex = newStr.indexOf(str.match(regex)[0]);
newStr.splice(0, vowelIndex, 'r');
return newStr.join('');
}
}
}
console.log(scoobyDoo('scOoby'));
I tested out the program again by capitalizing the first vowel index and instead of 'rooby' I get 'rscooby'. Why is that so?
Can you once try with following code in your else and see the changes
else {
var vowelIndex = newStr.indexOf(str.match(regex)[0]);
newStr.splice(0, vowelIndex, 'r');
return newStr.join("");
}
Is it not much easier like this? Or am I missing something??
'xylographer'.replace(/^\s*[^aieou]+(?=\w)/i,function(m,o,s){return "r"})
//"rographer"
'scooby'.replace(/^\s*[^aieou]+(?=\w)/i,function(m,o,s){return "r"})
//"rooby"
you could just use one reg expression for the whole algorithm and no need to split your string no more.
regexp to use.
/^[^aouie, AOUIE]+(?=[aouie, AOUIE])/g
of course you can readjust regexp to suit you more but this will get your main requirement.
On the line immediately after the else statement, I just called .toLowerCase() on it and it was fixed:
let vowelIndex = newStr.indexOf(str.match(regex)[0].toLowerCase());
I like keeping my code simple and readable
function pattern(str){
var vowelIndex = str.indexOf(str.match(/[aeiou]/)); //returns index of first vowel in str
return "r" + str.slice(vowelIndex);
}
console.log(pattern('xylographer'));

How to check if one element of an array matches another element in same array?

Very new to javascript so bear with me...
I need to check one element of an array(arr[1]), which contains a string, against another element of the same array(arr[0]) to determine if any letters included in element arr[1] are included in arr[0]. Those letters can be in any order, upper or lower case, and don't have to occur the same number of times (i.e. arr[0]="hheyyy" and arr[1]="hey" is fine). This is what i have (which works) but I was curious if anyone has a better/more simple way of doing this? -thanks in advance.
function mutation(arr) {
//splits the array into two separate arrays of individual letters
var newArr0 = arr.join('').toLowerCase().split('').slice(0,arr[0].length);
var newArr1 = arr.join('').toLowerCase().split('').slice(arr[0].length);
var boolArr = [];
//checks each letter of arr1 to see if it is included in any letter of arr0
for(var i = 0; i < newArr1.length; i++)
boolArr.push(newArr0.includes(newArr1[i]));
//results are pushed into an array of boolean values
if (boolArr.indexOf(false) !==-1)
return false; //if any of those values are false return false
else return true;
}
mutation(["hello", "hey"]); //returns false
You could use a regular expression:
function mutationReg(arr) {
return !arr[1].replace(new RegExp('['+arr[0].replace(/(.)/g,'\\\\$1')+']', "gi"), '').length;
}
This escapes every character in the second string with backslash (so it cannot conflict with regular expression syntax), surrounds it with square brackets, and uses that as a search pattern on the first string. Any matches (case-insensitive) are removed from the result, so that only characters are left over that don't occur in the second string. The length of the result is thus an indication on whether there was success or not. Applying the ! to it gives the correct boolean result.
This might not be the fastest solution.
Here is another ES6 alternative using a Set for good performance:
function mutation(arr) {
var chars = new Set([...arr[0].toLowerCase()]);
return [...arr[1].toLowerCase()].every (c => chars.has(c));
}
You can use Array.from() to convert string to an array, Array.prototype.every(), String.prototype.indexOf() to check if every charactcer in string converted to array is contained in string of other array element.
var arr = ["abc", "cab"];
var bool = Array.from(arr[0]).every(el => arr[1].indexOf(el) > -1);
console.log(bool);

Categories

Resources