Project

General

Profile

Bug #14114

Add #step for Array, Enumerable, Enumerator

Added by eike.rb (Eike Dierks) over 1 year ago. Updated about 1 year ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:83800]

Description

This must have been discussed before,
please reassigned and close this one.

I want to propose an extension to the ruby core api:

  • add #step(n) for Enumerable, Enumerator
  • coerce the step() api across all the api (see Numeric#step(by:n, to:m))
  • extend to: #step((by:n, to:m, offset:k)

We have Range#step,
but #step is not yet defined for Array, Enumerable, Enumerator

I believe, the semantics of #step, as defined in Range
applies like well for Enumerable and Enumerator

It should return every n'th element of the collection.

As of 2.4 #step is not yet defined for Enumerable/Enumerator.
But the semantics of this method is already well defined for Range.

That same semantics should also apply for Enumerable#step(n)
aka: pick every n'th element.

Examples:

('a'..'z').step(2)
=> #

('a'..'z').to_a.step(2)
NoMethodError: undefined method `step'

('a'..'z').to_enum.step(2)
NoMethodError: undefined method `step'

History

Updated by shevegen (Robert A. Heiler) over 1 year ago

I think you filed this in the wrong subsection (Bug rather than Feature).

To the topic - I am not sure if .step() makes a lot of sense to Array.

I have had a look at your suggestion but you do not give any example
for Array#step, for example, so it is difficult to know what exactly
you want to see.

Consider this Array:

array = %w( a b c 1 2 3 4 )

What would .step(2) return on this Array?

Updated by Hanmac (Hans Mackowiak) over 1 year ago

you might use each_slice for this:

module Enumerable
  def step(n)
    each_slice(n).map(&:first)
  end
end

Updated by shan (Shannon Skipper) about 1 year ago

Here's a pure Ruby implementation of Enumerable#step, just for fun:

module Enumerable
  def step step = 1
    raise TypeError, 'no implicit conversion of Object into Integer' unless step.respond_to? :to_int
    step_count = step.to_int
    raise TypeError, "can't convert #{step.class} to Integer (#{step.class}#to_int gives #{step_count.class})" unless step_count.instance_of? Integer
    raise TypeError, "step can't be 0" if step_count.zero?
    raise TypeError, "step can't be negative" if step_count.negative?

    if block_given?
      each_slice step_count do |this_step, *_rest|
        yield this_step
      end
      self
    else
      lazy_size = size.fdiv(step_count).ceil if size
      Enumerator.new lazy_size do |yielder|
        each_slice step_count do |this_step, *_rest|
          yielder << this_step
        end
      end
    end
  end
end

A gist of the same, with some specs mostly borrowed from Range#step: https://gist.github.com/havenwood/501b7a7e57f512ec95cfcb7f9a44f0d0

Also available in: Atom PDF