2

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.

0

4 Answers 4

5

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
Sign up to request clarification or add additional context in comments.

1 Comment

++ maybe your next answer can include some flossing examples ^,,,^
1

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.

Comments

1

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]

Comments

1

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

Comments

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.