VQV03
VQV03

Reputation: 43

Multiple Index Array - Ruby

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

Answers (4)

Stefan
Stefan

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

Chris
Chris

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

Cary Swoveland
Cary Swoveland

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

Robert Nubel
Robert Nubel

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

Related Questions