1

I'm using indexOf() to get the index of an element inside an array.

    fs.readFile(filename, { encoding: 'utf-8' }, (error, data) => {
        if( error ) return new Error(error);
        let selectedWord;
        let charMap = [];
        let splittedWord;
        let output;

        const d = data.split('\n');
        for(let i = d.length - 1; i > 0; i--){
            const r = Math.floor(Math.random() * (i + 1));
            const tmp = d[i];
            d[i] = d[r];
            d[r] = tmp;
            selectedWord = d[r];
        }
    
        splittedWord = selectedWord.split('');
        splittedWord.forEach( (char, i) => {
            charMap.push(char);
        });
        //console.log(charMap);    

        console.log(`Selected word length is ${splittedWord.length}`);
        
        let dropped = 0;
        let mid = Math.floor(splittedWord.length / 2);
        console.log(`Removed letters: ${mid}`);
        for (let e = 0; e < splittedWord.length; e++) {
            // probability we should drop this one is
            // (desiredDropped - dropped) / (word.length - e)
            if( Math.random() < (mid - dropped) / (splittedWord.length - e) ){
                // drop this letter
                splittedWord.splice(e, 1, '_');
                dropped++;
            }
        }
        
        //console.log(`Original word: ${selectedWord}`);
        console.log(`Guess the word: ${splittedWord}`);
        
        let removedChars = charMap.filter( (char, index) => {
            if( splittedWord[index] !== char ){
                return char;
            }
        });

        console.log(removedChars);

        let charIndex = -1;
        rl.prompt();
        rl.on('line', (input) => {
            //console.log(input);
            if( removedChars.includes(input) && charMap.includes(input) ){
                charIndex = charMap.indexOf(input, charIndex + 1);
                //console.log(charIndex);
                splittedWord.splice(charIndex, 1, input);
                // splittedWord = splittedWord.map( (char, i) => {
                //     charMap[i] === input ? input : char
                // });
                console.log(`Well done!Gues the next char: ${splittedWord}`);
                rl.prompt();
            } else if( splittedWord.join() === selectedWord ){ 
                console.log('Congratulation, you have found word!');
                rl.close();
                process.exit();
            } else {
                console.log(`Oh noo, wrong letter! Try with a different one!`);
                rl.prompt();
            }
        });

    });

I've noticed that if in the searched array there are two identical elements at a different index, only one will be found and the second will be ignored. Is there a solution for this?

Consider this concole output, I have the word grifferesti, since the letter i is present two times, indexOf will only give me the first element found that match the searching criteria. This become a problem because a word can contain duplied charatchers and I need to check for them if they are removed from the word that the user will display and try to guess

[
  'g', 'r', 'i', 'f',
  'f', 'e', 'r', 'e',
  's', 't', 'i'
]
Selected word length is 11
Removed letters: 5
Original word: grifferesti
Guess the word: g,r,_,_,f,e,r,e,_,_,_
[ 'i', 'f', 's', 't', 'i' ]
Please insert a letter: i
i
2
Well done!Gues the next char: g,r,i,_,f,e,r,e,_,_,_
Please insert a letter: f
f
3
Well done!Gues the next char: g,r,i,f,f,e,r,e,_,_,_
Please insert a letter: t
t
9
Well done!Gues the next char: g,r,i,f,f,e,r,e,_,t,_
Please insert a letter: i
i
2
Well done!Gues the next char: g,r,i,f,f,e,r,e,_,t,_
Please insert a letter: s
s
8
Well done!Gues the next char: g,r,i,f,f,e,r,e,s,t,_

UPDATE

I'm testing the solution provided from the user Hacktish but this is what happen in console when I test the code

Selected word length is 9
Your score: 0
Guess the word: _,_,_,_,_,_,_,_,_
Please insert a letter: a
You found the a letter!
Score: 50
,,,,,,,,
Please insert a letter: c
Oh noo!Wrong letter, please retry!
Please insert a letter: b
Oh noo!Wrong letter, please retry!
Please insert a letter: n
You found the n letter!
Score: 0
,,,,,,,,

As you can see, the _ char will be not displayed and the found letter will be not added. I don't know if this can be an implementation issue

splittedWord = charMap.map( () => '_' );

        rl.prompt();
        rl.on('line', (input) => {
            if( charMap.includes(input) ){
                splittedWord = splittedWord.map( (char,i) => {
                    charMap[i] === input ? input : char;
                });
                console.log(`You found the ${input} letter!`);
                score += 50;
                console.log(`Score: ${score}`);
                console.log(`${splittedWord}`);
                rl.prompt();
            } else if( splittedWord.join('') === selectedWord ){
                console.log('Yeah! You found the word!');
                
                rl.close();
            } else {
                console.log('Oh noo!Wrong letter, please retry!');
                score -= 50;
                rl.prompt();
            }
        });

9
  • 1
    That is the intended behaviour of indexOf - “ The indexOf() method returns the first index at which a given element can be found in the array” (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) Commented Apr 14, 2021 at 10:55
  • if you want to remove them all, use filter instead of indexOf + splice Commented Apr 14, 2021 at 10:55
  • @AnsonMiu - I thought about posting the comment like you did (at the same time), but then again, simple question = simple answer:) Commented Apr 14, 2021 at 10:57
  • You can pass in the second parameter to tell the function to start looking after the first found element. (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) Commented Apr 14, 2021 at 10:57
  • I don't need to remove the element but get the index and add it to the splittedWord array using splice Commented Apr 14, 2021 at 10:58

4 Answers 4

1

The other answers explain you about the behavior of indexOf. But this may not be the right function for you to use. You would need a lot of additional functionality to make this work with indexOf.

You can solve your problem with less code if you use another function:

splittedWords = splittedWords.map((_,i)=>charMap[i]===input?input:_);

The above code walks throuw the splitterWords array (map function), the _ is the element in the array, the i is the index. It replaces the element in the array by the result of the ternary operation: If it's original character matches the input, it is replaced by the input. Otherwise it is left the same.


EDIT:
example code to paste in your JS console

const charMap=[
  'k','i','w','i'
]
let splittedWords=charMap.map(()=>'_');
const input='i';
splittedWords=splittedWords.map((_,i)=>charMap[i]===input?input:_);
console.log(splittedWords);
// THIS PRINTS: ["_", "i", "_", "i"]
Sign up to request clarification or add additional context in comments.

7 Comments

This would also cause the input to be replaced with all the occurrences of that character for the same input. E.g. The first i given as input will be substituted to both the places even though the user has only given a single i as input.
splittedWord.splice(charIndex, 1, input); at the moment I'm replacing the underscore cha using splice(). The charMap array hold all the letters of the word before the letters are removed and the splittedWord contains the word after the letters are removed. I think that is better to map the charMap array and then replace the found letters inside the splittedWord array?
I'm testing with map but it will return only commas after the input Original word: agguatiate Guess the word: _,g,_,u,_,_,i,a,t,_ [ 'a', 'g', 'a', 't', 'e' ] Please insert a letter: g Well done!Gues the next char: ,,,,,,,,, As you can see all the letters will be lost
I edited my answer to include example code you can try. @MD.TabishMahfuz That's the intended behior because that's how it works with word guessing games. And otherwise, newbiedev didn't have to ask this question because his intial code already worked if the goal was to make the user enter the same character multiple times.
@Hacktish Yup I can confirm this behaviour had tested it myself. If this is the behaviour newbiedev wants then this is the best way to get it.
|
1

No, that's an intended behavior:

The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

2 Comments

As I've writed in a comment, I need to find the element index and then add it to the splittedWord array. I'm doing an exercise to improve my JS/nodejs skills and I'm making a guess the word game. :)
In such case, you can do a simple loop - (while indexOf > -1) and keep updating the string and array as you keep on iterating.
1

You need to write your own function for that behavior. indexOf returns first matching result, but as MD. Tabish Mahfuz explained already you can continue search by providing 2nd parameter (starting point) to find next matching element.

Basically something like this should work:

Array.prototype.indexesOf = function(element) {
  let allIndexes = []
  let index = -1
  //search for all matching elements in array until end of array is reached
  while ((index = this.indexOf(element, index + 1)) >= 0)
    allIndexes.push(index)
  return allIndexes
}

Then you can call it like this [1,2,1,1,3,4,5,2,3,1,1].indexesOf(1) which should return [0, 2, 3, 9, 10].

Note: Adding function to Array like that is called monkey patching, and in production you might not want to do that as code can get really messy. Instead you can create either wrapper class or function.

2 Comments

will this work with splice? or I need to call splice more than once to replace the underscore with the found char?
If you want to remove elements at all given indexes (returned from indexesOf()) with splice(), then you need to iterate array in reverse order, otherwise you indexes will mismatch after first removal. I am not sure if this is what you are asking, but something like this should work: let arr = [1,2,1,1,3,4,5,2,3,1,1]; arr.indexesOf(1).reverse().forEach(index => arr.splice(index, 1))
1

You can pass in a second parameter the the indexOf function to find elements after that specific index.

Shared from the documentation here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

console.log(beasts.indexOf('bison'));
// expected output: 1

// start from index 2
console.log(beasts.indexOf('bison', 2));
// expected output: 4

console.log(beasts.indexOf('giraffe'));
// expected output: -1

A simple function to get all the indexes of an element in the array:

function indexesOf(arr, el) {
    let i = -1, indexes = [];
    while ( (i = arr.indexOf(el, i+1)) != -1 )
        indexes.push(i);
    return indexes;
}

So what you can do is simply keep an index of the last added item in the splittedWord. and then pass in that index + 1 to the charMap.indexOf to get the correct position.

// Outside of your loop
let charIndex = -1;

// Inside the loop
....
....
if ( removedChars.includes(input) && charMap.includes(input) ) {
    charIndex = charMap.indexOf(input, charIndex + 1);
    ....
    ....

Something like this. Please test and correct this if necessary. I have NOT tested this.

Alternatively, try this:

if( removedChars.includes(input) && charMap.includes(input) ){
    let charIndex;
    do {
        charIndex = charMap.indexOf(input, charIndex + 1);
    } while( splittedWord[charIndex] != '_' || charIndex != -1 )
    console.log(charIndex);

    if ( charIndex == -1 ) {
        // Not sure if this can happen in your code
        // Shouldn't happen if you are removing 
        // the characters from removedChars array properly
    } else {
        splittedWord.splice(charIndex, 1, input);
        // splittedWord[charIndex] = input; Should also work I think.
    }
    console.log(`Well done! Guess the next char: ${splittedWord}`);
    rl.prompt();
} else {
    console.log(`Oh noo, wrong letter! Try with a different one!`);
    rl.prompt();
}

8 Comments

Would this custom function I have added in the edit work for you?
I don't have a loop, I'm simpy thesting the array after the readline return an input :). Anyway I will test and let you know, thank you.
Your if is running multiple iterations right? Just put the declaration one level above that scope so that the charIndex value persists through multiple calls to this line of code.
the if statement is inside the readline.on('line') callback and it's called only after user input
So just declare charIndex = -1 outside that callback function to make sure that it's value is persisted from the last call.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.