Reputation: 23
New to Ruby; When I am working with arrays, is there a difference between Enumerable#each_with_index
and Array#index
I have been working with a multidimensional array in which I am trying to find the location of a given value. My code is able to pass all the tests when I use either one. Coordinates is the array I am using to hold the location of the given value (in this case 1)
field=[[1,0],[0,0]]
coordinates=[]
field.each_with_index do |item|
if item.index(1)
coordinates.push(field.index(item)).push(item.index(1))
end
end
Thanks in advance for the help.
Upvotes: 2
Views: 1388
Reputation: 6076
Given that:
fields = [[1, 0], [0, 0]]
Enumerable#each_with_index passes each index as well as each object to the block:
fields.each_with_index do |field, idx|
puts "#{field}, #{idx}"
end
#=> [1, 0], 0
#=> [0, 0], 1
Array#index returns the index of the matching array element:
puts fields.index([1, 0])
#=> 0
Upvotes: 1
Reputation: 3009
Array#index
and Enumerable#each_with_index
are not related whatsoever (functionally speaking), one is used to get the index of an object within an Array and the other one is used to walk through a collection.
Actually each_with_index
is a mutation of the each
method that additionally yields the index of the actual object within the collection. This method is very useful if you need to keep track of the current position of the object you are in. It saves you the trouble of creating and incrementing an additional variable.
For example
['a', 'b', 'c', 'd'].each_with_index do|char, idx|
puts "#{idx}) #{char}"
end
output:
0) a
1) b
2) c
3) d
Without each_with_index
idx = 0
['a', 'b', 'c', 'd'].each do|char|
puts "#{idx}) #{char}"
idx += 1
end
output:
0) a
1) b
2) c
3) d
To find the index of an object within a multidimensional Array you will have to iterate at least through all the rows of the matrix, like this (using each_with_index
and index
)
def find_position(matrix, obj)
matrix.each_with_index do|row, i|
return [i, j] if j = row.index(obj)
end
return nil
end
for example:
find_position([[2,3],[4,5]], 5) # => [1, 1]
Upvotes: 1
Reputation: 110645
Let's take a closer look at your code:
field=[[1,0],[0,0]]
coordindates = []
field.each_with_index do |item|
if item.index(1)
coordinates.push(field.index(item)).push(item.index(1))
end
end
Let:
enum = field.each_with_index
#=> #<Enumerator: [[1, 0], [0, 0]]:each_with_index>
As you see this returns an enumerator.
Ruby sees your code like this:
enum.each do |item|
if item.index(1)
coordinates.push(field.index(item)).push(item.index(1))
end
end
The elements of the enumerator will be passed into the block by Enumerator#each, which will call Array#each since the receiver, field
is an instance of the class Array
.
We can see the elements of enum
by converting it to an array:
enum.to_a
#=> [[[1, 0], 0], [[0, 0], 1]]
As you see, it has two elements, each being an array of two elements, the first being an array of two integers and the second being an integer.
We can simulate the operation of each
by sending Enumerator#next to enum
and assigning the block variables to the value returned by next
. As there is but one block variable, item
, we have:
item = enum.next
#=> [[1, 0], 0]
That is quite likely neither what you were expecting nor what you wanted.
Next, you invoke Array#index on item
:
item.index(1)
#=> nil
index
searches the array item
for an element that equals 1
. If it finds one it returns that element's index in the array. (For example, item.index(0) #=> 1
). As neither [1,0]
nor 0
equals 1
, index
returns nil
.
Let's rewind (and recreate the enumerator). You need two block variables:
field.each_with_index do |item, index|...
which is the same as:
enum.each do |item, index|...
So now:
item, index = enum.next
#=> [[1, 0], 0]
item #=> [1, 0]
index #=> 0
and
item.index(1)
#=> 0
I will let you take it from here, but let me mention just one more thing. I'm not advocating it, but you could have written:
field.each_with_index do |(first, second), index|...
in which case:
(first, second), index = enum.next
#=> [[1, 0], 0]
first #=> 1
second #=> 0
index #=> 0
Upvotes: 5
Reputation: 4989
See Ruby doc: http://ruby-doc.org/core-2.2.0/Array.html#method-i-index and http://ruby-doc.org/core-2.2.1/Enumerable.html#method-i-each_with_index
index
is a method of Array
, it detects whether an item exists in a specific Array
, return the index if the item exists, and nil
if does not exist.
each_with_index
is a method of Enumerable
mixin, it usually takes 2 arguments, first one is item and the second one is index.
So your sample could be simplified as:
field = [[1, 0], [0, 0]]
coordinates = []
field.each_with_index do |item, index|
item_index = item.index(1)
coordinates << index << item_index if item_index
end
puts coordinates.inspect # => [0, 0]
Note your field.index(item)
is just index
.
Upvotes: 1