Reputation: 43
i have a project of a game, in it i need to impress the letter selected if the letter selected is present in the secret word, but when i try to catch the index of the select letter, like "a", in the secret word "programador, the code returns me all the index of the word "programador" not only the index where the letter "a" is present.
palavra_secreta = "programador"
letra_procurada = "a"
total_encontrado = palavra_secreta.count letra_procurada
palavra_secreta_array = palavra_secreta.split("")
puts palavra_secreta_array.each_with_index.select { |letra_procurada, index|
total_encontrado >= 1
}.map { |pair| pair[1] }
this code is returning me: 0 1 2 3 4 5 6 7 8 9 10
Upvotes: 1
Views: 120
Reputation: 114138
"letra_procurada"
means "letter wanted", so you seem to be under the impression that you have to pass the element you're looking for to select like this:
letra_procurada = "a"
palavra_secreta_array.each_with_index.select { |letra_procurada, index| }
# ^^^^^^^^^^^^^^^
However, this is not at all how block arguments or select
in particular work. If you would replace letra_procurada
by a string literal, you'd get a SyntaxError
right-away:
palavra_secreta_array.each_with_index.select { |"a", index| }
# SyntaxError: unexpected string literal
A block contains code that the method you pass the block to (here: select
) can invoke (multiple times, just once or not at all). Upon each block invocation, the block arguments (|letra_procurada, index|
) are passed from the method (i.e. from select
) into the block.
You call select
, but select
calls the block. This is also known as a "callback".
In your example, select
invokes the block for each element and passes that element as the block arguments letra_procurada
and index
. It then expects the block to return either true
or false
. That return value determines whether or not that element will be included in the result.
Since the block's first argument can be any letter, you likely want a more general variable name. Something like this:
palavra_secreta_array.each_with_index.select { |letra, index|
# ...
}
Some examples might help to understand this. To select only elements with an odd?
index:
palavra_secreta_array.each_with_index.select { |letra, index|
index.odd?
}
#=> [["r", 1], ["g", 3], ["a", 5], ["a", 7], ["o", 9]]
To select only elements whose letter is equal to "r"
or equal to "o"
: (||
means "or")
palavra_secreta_array.each_with_index.select { |letra, index|
letra == "r" || letra == "o"
}
#=> [["r", 1], ["o", 2], ["r", 4], ["o", 9], ["r", 10]]
Or combined, to select only elements with a letter of "r"
or "o"
and an odd index: (&&
means "and")
palavra_secreta_array.each_with_index.select { |letra, index|
(letra == "r" || letra == "o") && index.odd?
}
#=> [["r", 1], ["o", 9]]
As you can see, using a block is a very flexible and powerful way to select elements.
Back to your problem. Your current block just checks whether total_encontrado
is larger or equal to 1
. It doesn't take the current letter or its index into account. And because total_encontrado
is larger than 1
, the block always returns true
and select
returns all elements.
To select only elements whose letter match the wanted letter letra_procurada
, you'd use:
palavra_secreta_array.each_with_index.select { |letra, index|
letra == letra_procurada
}
#=> [["a", 5], ["a", 7]]
Upvotes: 0
Reputation: 36486
In your code, total_encontrado
is 2
, which is greater than or equal to 1
. As a result, the condition you give to #select
is always true, so it selects every character with its index. You then map that to just the indices.
Instead, you likely want to select only the letters that match letra_procurada
.
palavra_secreta = "programador"
letra_procurada = "a"
total_encontrado = palavra_secreta.count letra_procurada
palavra_secreta_array = palavra_secreta.split("")
puts palavra_secreta_array
.each_with_index
.select { |letra, index| letra_procurada == letra }
.map { |pair| pair[1] }
You could also use #filter_map
(Ruby 2.7 and later) to simplify this.
palavra_secreta = "programador"
letra_procurada = "a"
total_encontrado = palavra_secreta.count letra_procurada
palavra_secreta_array = palavra_secreta.split("")
puts palavra_secreta_array
.each_with_index
.filter_map { |letra, index| index if letra_procurada == letra }
Upvotes: 1
Reputation: 110665
When dealing with problems where an array of indices of a collection or string whose corresponding elements satisfy a particular condition is to be returned, it is often convenient to work with an array of indices [0,1,..,n]
. For example, if one wanted to return an array of indices of elements of the array arr = [3,2,6,5,4]
that are odd numbers, one might write one of the following1:
arr.each_index.select { |i| arr[i].odd? }
(0..arr.size-1).select { |i| arr[i].odd? }
arr.size.times.select { |i| arr[i].odd? }
All return [0, 3]
.
For the present problem, involving a string, we could use either of the last two approaches above:
(0..palavra_secreta.size-1).select { |i| palavra_secreta[i] == letra_procurada }
palavra_secreta.size.times.select { |i| palavra_secreta[i] == letra_procurada }
Both return [5, 7]
.
I believe this approach reads well, as it should be immediately apparent to the reader that a selection of indices is being returned.
1. I realize that in #2 I could have written (0...arr.size)
, but I have found that mixing two- and three-dot ranges tends to spawn bugs, so I only use three-dot ranges when a two-dot range cannot be used.
Upvotes: 2
Reputation: 7522
The condition inside your select
clause will be true for every index, since it only checks the total_encontrado
variable and that value will not change in each iteration.
puts palavra_secreta_array
.each_with_index
.select { |letra_procurada, index| total_encontrado >= 1 }
# ^^^^^^^^^^^^^^^^^^^^^
.map { |pair| pair[1] }
What you want is to select the indexes that match the guessed character, so your condition could do that instead:
puts palavra_secreta_array
.each_with_index
.select { |letra, index| letra == letra_procurada }
.map { |pair| pair[1] }
Note that I had to change the name of the first parameter to the select
block. You had re-used letra_procurada
there, but that would have the effect of shadowing the actual letra_procurada
variable. Remember that select
is calling the block for each element in the array and selecting only the elements for which the that block returns true, so the first parameter to the block is the element from the array and, if you shadow or ignore it, you'll have no idea what you're dealing with inside the block.
You can also slim this down a bit more and skip the split
in favor of using String#each_char
:
puts palavra_secrata
.each_char
.with_index
.select { |c, _| c == letra_procurada }
.map(&:last)
Upvotes: 1