Dmitry Dmitriev
Dmitry Dmitriev

Reputation: 1059

Ruby Split array for two with partition and with_index

Can you give explanation of logic or algorithm of ruby behaviour for this construction:

arr = [1,2,3,4,5]
arr.partition.with_index{|_,index| index>2} 

How to formalize logic when iterate through Enumerable give 2 arrays output. When we just call single partition its clear - just method behavior, but when it trails by with_index this construction become "Magical" for me.

Thank you

UPD: The condition is not in block of partition, it is in separate 'Enumerable' Object method block. This method is with_index. This second level of interaction is interesting for me. Why do conditions of with_index have influence on partition result? This is a behavior that is not clear from partition documentation.

Upvotes: 2

Views: 5866

Answers (4)

Daniel Viglione
Daniel Viglione

Reputation: 9437

You are conflating the Enumerable module and the Enumerator class. All methods in the module Enumerable are instance methods that require their receiver to be an enumerator (an instance of the class Enumerator).

You use "include Enumerable" in your custom class and implement an each method which returns an enumerator:

def each
  return enum_for(:each) unless block_given?

  @apples.each { |apple| yield apple }
end

When a method contained in Enumerable is executed on an instance of a class that includes Enumerable, Ruby inserts the method each IMPLICITLY between the instance and the Enumerable method.

with_index is NOT a method of Enumerable module. It is an instance method of the Enumerator class. Now remember I stated that when you include Enumerable module in your custom class and implement an each method, that each method can be used to return an enumerator instance. So, classes that have a method each that returns an enumerator can make use of the method Enumerator#with_index by (explicitly) inserting each between an instance of the class and with_index. Or it can invoke with_index on the enumerable module method since that returns an enumerator instance:

arr = [1,2,3,4,5]
arr.partition.with_index{|_,index| index>2} 

In the above example, partition is an instance method of Enumerable module and with_index is an instance method of Enumerator class. partition returns an enumerator instance which can take with_index.

Now for understanding what this statement does, you can simply look at the Enumerable docs which explains what partition does:

Returns two arrays, the first containing the elements of enum for which the block evaluates to true, the second containing the rest.

So in your case, it is determining whether the index is greater than 2. If it is, it gets put in one array, otherwise the other.

Upvotes: 0

mikej
mikej

Reputation: 66283

You've probably read in the Ruby docs for partition:

If no block is given, an enumerator is returned instead.

> arr = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
> arr.partition
=> #<Enumerator: ...>

You'll see the same thing in the description for a lot of the methods in Enumerable but there isn't much detail about the enumerator that's returned.

The key to the enumerator chaining is in the the behaviour of the each method in the Enumerator that's returned. For example, the Enumerator returned by partition has an each method that behaves like partition. This is how you're able to pass a single block and get the combined behaviour of partition and with_index. If you do this in a couple of steps it might help:

> enumerator = arr.partition
=> #<Enumerator: ...>
> enumerator.each { |n| n < 3 } # just to demonstrate `each` performing a partition
=> [[1, 2], [3, 4, 5]]
> enumerator.with_index { |_, index| index > 2 }
=> [[4, 5], [1, 2, 3]]

Another way to think about it is that partition with no block given is like passing :partition to enum_for e.g.

> another_enum = arr.enum_for(:partition)
=> #<Enumerator: ...>
> another_enum.with_index { |_, index| index > 2 } # same result as above
=> [[4, 5], [1, 2, 3]]

Upvotes: 1

Mikhail Katrin
Mikhail Katrin

Reputation: 2384

partition

Returns two arrays, the first containing the elements of enum for which the block evaluates to true, the second containing the rest. If no block is given, an enumerator is returned instead.

arr = [1,2,3,4,5]
arr.partition.with_index{|_,index| index>2} 
=> [[4, 5], [1, 2, 3]]

First array will contains [4,5] because it satisfys the condition index > 2.

Second array will contains all other elements: [1,2,3]

Upvotes: 1

Safi Nettah
Safi Nettah

Reputation: 1170

When the index is > 2 ruby create a new array with the rest of values

Upvotes: 1

Related Questions