GNS
GNS

Reputation: 23

Use case of each_with_index vs index in Ruby

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

Answers (4)

seph
seph

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

yeyo
yeyo

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

Cary Swoveland
Cary Swoveland

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

coderz
coderz

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

Related Questions