I'm a newbie messing around with event listener/handlers and generally just getting to grips with some JS (first post on StackOverflow!)
I'm making a simple webpage that changes backgroundColor based on user input (alpha characters only). The user presses a letter key and the page will update to a color with a match on the first letter. It will also print the name of the color to the page.
I have a large array storing names of CSS colors, most keys have multiple colors that match the keystroke/first letter of the color, and some keys have no matches.
My code below hopefully explains my intentions, however I can't quite get it to work.
const colors = ["Aquamarine", "Alice Blue", "Antiquewhite", "Aqua", "Azure"] etc etc...
// Returns a random colour from the colour arrays
const randomiser = colorArr => {
return colorArr[Math.floor(Math.random()*colorArr.length)];
}
// Event listener
window.addEventListener("keypress", e => {
let colorsMatchingKeyName = [];
function colorFinder(arr) {
let keyName = e.key;
for (let i = 0; i < arr.length; i++) {
if (arr[i].charAt(0) !== keyName) {
document.getElementById('currentColor').innerHTML = `Sorry! No colours match up with the ${keyName} key!`;
} else if (arr[i].charAt(0) == keyName) {
colorsMatchingKeyName.push(arr[i]);
}
}
}
colorFinder(colors);
// Logging colors to console to test.
console.log(colorsMatchingKeyName) // outputs an empty array
// Once it is working i'll use the randomiser() function to pick a
// random colour from the colorMatchingKeyName array and set the document.style.backgroundColor
})
At present, each key event only prints the 'Sorry! No colours...' message shown in the if statement.
I'd be hugely grateful if someone could please direct me to the issue in my code!
You might want to check this out:
const colors = ["Aquamarine", "Alice Blue", "Antiquewhite", "Aqua", "Azure"]
// Returns a random colour from the colour arrays
const randomiser = colorArr => {
return colorArr[Math.floor(Math.random()*colorArr.length)];
}
// Event listener
window.addEventListener("keypress", e => {
let colorsMatchingKeyName = [];
function colorFinder(arr) {
let keyName = e.key;
document.getElementById('currentColor').innerHTML = '';
for (let i = 0; i < arr.length; i++) {
const c = arr[i].charAt(0).toLowerCase();
if (c !== keyName.toLowerCase()) {
document.getElementById('currentColor').innerText = `Sorry! No colours match up with the ${keyName} key!`;
} else if (c == keyName) {
colorsMatchingKeyName.push(arr[i]);
}
}
}
colorFinder(colors);
const color = randomiser(colorsMatchingKeyName);
console.log(color)
document.body.style.backgroundColor = color;
})
<div id="currentColor"></div>
Related
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
expected_x=[1,2,6,3,5,5,4]
expected_y=[3,4,3,5,2,4,6]
Think of x and y as coordinates.[1,3] will be first point and [4,6] will be last point.
If a [X,Y] has duplicates, only one of the [X,Y] will be displayed in the expected output (no duplicate). And if, there is a mirror like [X,Y] which is a mirror of [Y,X] with both at the same index.
This is the code I have written for just one array to make the array unique. However, I am unsure on how to use it with 2 seperate arrays representing x and y coordinates. Any help will be appreciated :)
let chars = ['A', 'B', 'A', 'C', 'B'];
let uniqueChars = [...new Set(chars)];
console.log(uniqueChars);
Use this:
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
const coordinates = [];
let i = -1;
while ( x[++i] ) {
const c = {
index: i,
value: [x[i], y[i]]
}
coordinates.push(c);
}
const coordArray = coordinates.reduce((p, next) => {
if (!p.values.includes(JSON.stringify(next.value)) && !p.values.includes(JSON.stringify([...next.value].reverse()))) {
p.values.push(JSON.stringify(next.value));
p.indexes.push(next.index);
}
return p;
},{
indexes: [],
values: []
})
coordArray.values = coordArray.values.map(JSON.parse)
console.log(coordArray)
You can use a for loop and iterate both arrays together, since they have the same length (being an x,y pair) to each other.
You can also keep a "history" of duplicates and mirrors. Then all you need to do while iterating is check the history. If there is no match, append the current to the result arrays, then update the history.
let x=[1,2,6,3,5,5,5,4,4];
let y=[3,4,3,5,2,4,4,2,6];
let h=[]; // history
let rx = []; // result x
let ry = []; // result y
for (let i = 0; i < x.length && i < y.length; i++) {
// The if line (with include()) would be nice if it worked, but it didn't because of
// always returning false.
// Instead I will have to manually search.
// if (h.includes([x[i], y[i]]) || h.includes([y[i], x[i]])) {
let found = false;
for (let s = 0; s < h.length; s++) {
// check for duplicate
if (h[s][0] == x[i] && h[s][1] == y[i]) {
found = true;
break;
}
// check for mirror
if (h[s][0] == y[i] && h[s][1] == x[i]) {
found = true;
break;
}
}
if (found) {
// do nothing, its a duplicate or mirror
console.log("duplicate or mirror detected on index " + i);
}
else {
// update results
rx.push(x[i]);
ry.push(y[i]);
// update history
h.push([ x[i], y[i] ]);
}
}
console.log("rx: " + rx);
console.log("ry: " + ry);
In short, .include() would have been nice, but apparantly the array by reference broke my intended logic. I don't know. But the above separated those concerns out by a literal search of "history", which would alter the "found" boolean to know whether a duplicate or mirror existed.
Obviously this code could like be shortened into less than 10 or 7 lines, but I wanted to work on it because it was interesting and the approach used demonstrates how regular for loops could be used to solve such "iteration" problems.
Hopes it helps.
So Im pretty new to javascript and coding in general. Im making a Wordle algorithm just for fun to build my skills in coding. and while making this algorithm i realized that i was going to have to kind of recreate Wordle. so i looked online for wordle recreations in java script, and all the ones I found I saw one flaw that I didn't want to have.
The flaw that I saw was in the IF statement for checking a letter in the wordle answer. Specifically when the letter is in the word but not in the right spot. The recreations that i saw had an IF statement that looked something like this.
IF (answerWord.includes(guessLetter[i])) {
guessLetter[i] = color.(yellow)
}
(this isnt exactly how they wrote it, but its the main idea)
My main focus is on the .includes. This would not work because say our guess word is "agree" and the answer word "ready". the 2 E's in "agree" would be yellow, and we dont want that because their is is only 1 E in "ready. So we only want the first E in "agree" to be yellow and not the second E.
So i decided to figure it out on my own, and i was able to come up with this and it works. At least Im pretty sure it works, base on the the many words i tested it on.
So my question is, since Im making an algorithm im going to be making alot of calculations, is this the most efficient i could write this or can i make it better?
let guessWord = "agree"; // the first word thats inputed
let answerWord = "ready"; // the answer to the wordle
/*
'letterCheck' is an array that tells what condition each letter in 'startLetter' is, based on the answer word
2 = the letter is in the word and in the correct place (GREEN)
1 = the letter is in the word but not in the correct place (YELLOW)
0 = the letter in not in the word (GREY)
*/
var letterCheck = ["0", "0", "0", "0", "0"];
var guessLetter = ["A", "A", "A", "A", "A"]; // the separated letters of 'startword' in a array
var answerLetter = ["A", "A", "A", "A", "A"]; // the separated letters of 'answord' in a array
//adds the start word and the answer world to the arrays
for (var i = 0; i < 5; i++) {
guessLetter[i] = guessWord.substring(i, i + 1);
answerLetter[i] = answerWord.substring(i, i + 1);
}
console.log(guessLetter);
console.log(letterCheck);
console.log(answerLetter);
//this loops goes though every letter one by one
for (var i = 0; i < 5; i++) {
//checks if the letter is in the right spot
if (guessLetter[i] == answerLetter[i]) {
letterCheck[i] = "2";
console.log(guessLetter[i]);
console.log(letterCheck);
} else if (answerWord.includes(guessLetter[i])) {
//checks if there is more than one letter in start word or its the first letter
if (guessWord.split(guessLetter[i]).length - 1 == 1 || i == 0) {
letterCheck[i] = "1";
console.log(guessLetter[i]);
console.log(letterCheck);
//checks if the the amount of same letters in start words is equel to the amount of same letters in answer word
} else if (guessWord.split(guessLetter[i]).length - 1 == answerWord.split(guessLetter[i]).length - 1) {
letterCheck[i] = "1";
console.log(guessLetter[i]);
console.log(letterCheck);
//opposite of above
} else if (guessWord.split(guessLetter[i]).length - 1 != answerWord.split(guessLetter[i]).length - 1) {
letterCheck[i] = "1";
console.log(guessLetter[i]);
console.log(letterCheck);
// checks if any of the letters infront of it are the same as it
for (var j = 0; j < i; j++) {
if (guessLetter[i] == guessLetter[j]) {
letterCheck[i] = "0";
console.log(guessLetter[i]);
console.log(letterCheck);
}
}
}
} else {
letterCheck[i] = "0";
console.log(guessLetter[i]);
console.log(letterCheck);
}
}
console.log(guessLetter);
console.log(letterCheck);
console.log(answerLetter);
( I will remove the console.log's later they just helped my to debug.)
Sorry if my code might be confusing, im not the best at keeping my code clean. if you have any confusion or questions, I'll try my best in clear up any confusion.
To get all of the states (in precedence order: correct, wrong-position, incorrect), you should check in that precedence order, and remove letters from contention once the state in that letter's position has been set.
function check(guess, targetWord) {
const checkStates = new Array(5).fill("incorrect");
const letters = guess.split('');
const targetLetters = targetWord.split('');
for (let i=0; i<5; i++) {
if (targetLetters[i] === letters[i]) {
checkStates[i] = "correct";
targetLetters[i] = '';
letters[i] = '';
}
}
for (let i=0; i<5; i++) {
if (!letters[i]) continue;
let fi = targetLetters.findIndex(t => t===letters[i]);
if (fi > -1) {
checkStates[i] = "contains";
targetLetters[fi] = '';
}
}
return checkStates;
}
let solution = "ready"
console.log(`solution is ${solution}`);
let guess = "agree";
console.log(`checking ${guess}... ${check(guess, solution)}`);
guess = "roars";
console.log(`checking ${guess}... ${check(guess, solution)}`);
guess = "boars";
console.log(`checking ${guess}... ${check(guess, solution)}`);
solution = "beast"
console.log(`\nsolution is ${solution}`);
guess = "treat";
console.log(`checking ${guess}... ${check(guess, solution)}`);
Well, here's something to consider in simplifying. You can use a simple MAP loop to determine which letters are incorrect, then later highlight those in the word.
let guessWord = "agree"; // the first word thats inputed
let answerWord = "ready"; // the answer to the wordle
let tmpGuess = guessWord.split('');
let incorrects = answerWord.split('').map(e => {
let i = tmpGuess.indexOf(e);
if (i > -1) { // found it!
tmpGuess.splice(i, 1); // remove
return '';
}
return e;
}).filter(f => f);
console.log(incorrects);
I'm using Javascript to replace elements in an array from a condition in another array.
I also need the final output to have the "" removed from any element which is replaced.
I have an array, tagArray which generates parts of speech for a given sentence theSentenceToCheck and it looks like this.
tagArray DET,ADJ,NOUN,VERB,ADP,DET,ADJ, NOUN ,ADP,DET,ADJ,NOUN
theSentenceToCheck The red book is in the brown shelf in a red house
I was able to write something that works and generates the desired output but its kinda redundant and total spaghetti.
I've looked at similar questions and tried other approaches using filter, map without success, especially on how to use those approaches and remove the "" for replaced elements.
This is my approach
var grammarPart1 = "NOUN";
var grammarPart2 = "ADJ";
var posToReplace = 0;
function assignTargetToFillIn(){
var theSentenceToCheckArrayed = theSentenceToCheck.split(" ");
var results = [];
var idx = tagArray.indexOf(grammarPart1);
var idx2 = tagArray.indexOf(grammarPart2);
while (idx != -1 || idx2 != -1) {
results.push(idx);
results.push(idx2)
idx = tagArray.indexOf(grammarPart1, idx + 1);
idx2 = tagArray.indexOf(grammarPart2, idx2 + 1);
posToReplace = results;
}
const iterator = posToReplace.values();
for (const value of iterator) {
theSentenceToCheckArrayed[value] ="xtargetx";
}
var addDoubleQuotesToElements = "\"" + theSentenceToCheckArrayed.join("\",\"") + "\"";
var addDoubleQuotesToElementsArray = addDoubleQuotesToElements.split(",");
/**This is where I remove the "" from element replaced with xtargetx*/
const iterator2 = posToReplace.values();
for (const value of iterator2) {
addDoubleQuotesToElementsArray[value] ="xtargetx";
console.log(value);
}
return results;
}
This gives me the desired output
"The",xtargetx,xtargetx,"is","in","the",xtargetx,xtargetx,"in","a",xtargetx,xtargetx
I was wondering what would be a more elegant solution or pointers on which other JS functions to look into.
A more idiomatically correct way to do this leveraging array methods might be like this.
Array.split(" ") splits a sentance into words
Array.filter(word => word.length) removes any value whose length is zero
Array.map((word, index) => {...}) iterates over the array and allows you to keep track of the current index value
Array.includes(element) simply tests that the array includes the value
Array.join(' ') does the opposite of Array.split(' ')
const tagArray = ["DET", "ADJ", "NOUN", "VERB", "ADP", "DET", "ADJ", "NOUN", "ADP", "DET", "ADJ", "NOUN"];
// Split on spaces and remove any zero-length element (produced by two spaces in a row)
const sentanceToCheck = "The red book is in the brown shelf in a red house".split(" ").filter(word => word.length);
const replaceTokens = ["ADJ", "NOUN"];
const replacementWord = "XXX";
const maskedSentance = sentanceToCheck.map((word, index) => {
const thisTag = tagArray[index];
if ( replaceTokens.includes(thisTag) ) {
return replacementWord;
} else {
return word;
}
}).join(' ');
console.log( maskedSentance );
I am creating Hangman game. I want when the button is clicked to be checked whether some of the hidden letters matches the clicked button
for(let i = 0; i < letters.length; i++){
let letter = letters[i]
let hiddenLetters = document.querySelectorAll('.hidden')
hiddenLetters = Array.from(hiddenLetters)
letter.addEventListener('click', ()=>{
let check = hiddenLetters.some((hiddenLetter)=>{hiddenLetter.textContent == letter.textContent})
if(check){
console.log('works')
} else{
console.log('doesnt')
}
})
}
I think you have your hiddenLetters in a string. I would suggest you store individual letters in an array that way its easier to compare/check if the letters exist.
Something like this should get you going.
const checkIfLettersExist = (hiddenLetters, letters) => {
return hiddenLetters.some(hdnLetter => letters.includes(hdnLetter));
}
const hiddenText = "name";
const hiddenLetters = hiddenText.split('');
let letters = ['a','b'];
const check = checkIfLettersExist(hiddenLetters, letters);
(check ? console.log("works") : console.log("doesn't"));
I am working on dictionary application written with react-native.
When I want to filter the array from the search box, I wrote below function. This is working quite good when I test with 2000 word list. But when the word list goes to thousands the search speed is really slow.
So, how can I improve this search function?
//Filter array when input text (Search)
let filteredWords = []
if(this.state.searchField != null)
{
filteredWords = this.state.glossaries.filter(glossary => {
return glossary.word.toLowerCase().includes(this.state.searchField.toLowerCase());
})
}
There are multiple factors that are making this code slow:
You're using filter() with a lambda. This adds a function call overhead for each item being searched.
You're calling toLowercase() on both strings before calling includes(). This will allocate two new string objects for every comparison.
You're calling includes. For some reason the includes() method is not as well optimized in some browsers as indexOf().
for loop (-11%)
Instead of using the filter() method, I recommend creating a new Array and using a for loop to fill it.
const glossaries = this.state.glossaries;
const searchField = this.state.searchField;
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (glossaries[i].toLowerCase().includes(searchField.toLowerCase())) {
filteredWords.push(glossaries[i]);
}
}
toLowerCase allocations (-45%)
Memory allocation is expensive due to the fact that JavaScript uses garbage collection mechanism for freeing used memory. When a garbage collection is performed the whole program is paused while it tries to finds memory which is not used anymore.
You can get rid of the toLowerCase() (inside the search loop) completely by making a copy of the glossary everytime the glossary is updated, which I assume is not often.
// When you build the glossary
this.state.glossaries = ...;
this.state.searchGlossaries = this.state.glossaries.map(g => g.toLowerCase());
You can also remove the toLowerCase() on the searchText by calling it once before the loop. After these changes, the code will look like:
const glossaries = this.state.glossaries;
const searchGlassaries = this.state.searchGlossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (searchGlassaries[i].includes(searchField)) {
filteredWords.push(glossaries[i]);
}
}
indexOf() instead of includes() (-13%)
I am not really sure why this is the case, but tests show that indexOf is a lot faster than includes.
const glossaries = this.state.glossaries;
const searchGlassaries = this.state.searchGlossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
for (let i = 0; i < glossaries.length; i++) {
if (searchGlassaries[i].indexOf(searchField) !== -1) {
filteredWords.push(glossaries[i]);
}
}
Overall the performance has improved by 70%.
I got the performance percentages from https://jsperf.com/so-question-perf
Optimize the algorithm
In the comments you said you would like an example of optimizations that can be done when the requirements are loosened to only match words that start with the search text. One way to do this is a binary search.
Let's take the code from above as starting point. We sort the glossaries before we store it in the state. For sorting case insensitively, JavaScript exposes the Intl.Collator constructor. It provides the compare(x, y) method that returns:
negative value | X is less than Y
zero | X is equal to Y
positive value | X is greater than Y
And the resulting code:
// Static in the file
const collator = new Intl.Collator(undefined, {
sensitivity: 'base'
});
function binarySearch(glossaries, searchText) {
let lo = 0;
let hi = glossaries.length - 1;
while (lo <= hi) {
let mid = (lo + hi) / 2 | 0;
let comparison = collator.compare(glossaries[mid].word, searchText);
if (comparison < 0) {
lo = mid + 1;
}
else if (comparison > 0) {
hi = mid - 1;
}
else {
return mid;
}
}
return -1;
}
// When you build the glossary
this.state.glossaries = ...;
this.state.glossaries.sort(function(x, y) {
return collator.compare(x.word, y.word);
});
// When you search
const glossaries = this.state.glossaries;
const searchField = this.state.searchField.toLowerCase();
const filteredWords = [];
const idx = binarySearch(glossaries, searchField);
if (idx != -1) {
// Find the index of the first matching word, seeing as the binary search
// will end up somewhere in the middle
while (idx >= 0 && collator.compare(glossaries[idx].word, searchField) < 0) {
idx--;
}
// Add each matching word to the filteredWords
while (idx < glossaries.length && collator.compare(glossaries[idx].word, searchField) == 0) {
filteredWords.push(glossaries[idx]);
}
}
As the question doesn't seem to belong on CodeReview, I think there are a few things that you can do to make your code drastically faster [citation needed]:
Cache that call to this.state.searchField.toLowerCase() as you don't need to call it on every iteration.
Use regular old for loops instead of flashy-but-slow Array functions.
And here is the final result:
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
theArray = this.state.glossaries; // cache this too
for(let i = 0, l = theArray.length; i < l; ++i) {
if(theArray[i].word.toLowerCase().includes(searchField)) {
filteredWords.push(theArray[i]);
}
}
}
Edit:
If you want to search for glossaries whose word start with searchField, then use indexOf === 0 instead of includes as the condition like this:
if(theArray[i].word.toLowerCase().indexOf(searchField) === 0) {