Skip to content

Commit

Permalink
Add Enumerable#each_step and Iterable#each_step (crystal-lang#13610)
Browse files Browse the repository at this point in the history
Co-authored-by: Sijawusz Pur Rahnama <[email protected]>
  • Loading branch information
baseballlover723 and Sija authored Oct 18, 2023
1 parent 21c55ff commit 9e625d5
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
38 changes: 38 additions & 0 deletions spec/std/enumerable_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "spec"
require "spec/helpers/iterate"

private class SpecEnumerable
include Enumerable(Int32)
Expand Down Expand Up @@ -393,6 +394,43 @@ describe "Enumerable" do
end
end

describe "each_step" do
it_iterates "yields every 2nd element", %w[a c e], %w[a b c d e f].each_step(2)
it_iterates "accepts an optional offset parameter", %w[b d f], %w[a b c d e f].each_step(2, offset: 1)
it_iterates "accepts an offset of 0", %w[a c e], %w[a b c d e f].each_step(2, offset: 0)
it_iterates "accepts an offset larger then the step size", %w[d f], %w[a b c d e f].each_step(2, offset: 3)

it_iterates "accepts a step larger then the enumerable size", %w[a], %w[a b c d e f].each_step(7)
it_iterates "accepts an offset larger then the enumerable size", %w[], %w[a b c d e f].each_step(1, offset: 7)

it "doesn't accept a negative step" do
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(-2)
end
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(-2) { }
end
end

it "doesn't accept a step of 0" do
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(0)
end
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(0) { }
end
end

it "doesn't accept a negative offset" do
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(2, offset: -2)
end
expect_raises(ArgumentError) do
%w[a b c d e f].each_step(2, offset: -2) { }
end
end
end

describe "each_with_index" do
it "yields the element and the index" do
collection = [] of {String, Int32}
Expand Down
38 changes: 38 additions & 0 deletions src/enumerable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,44 @@ module Enumerable(T)
nil
end

# Iterates over the collection, yielding every *n*th element, starting with the first.
#
# ```
# %w[Alice Bob Charlie David].each_step(2) do |user|
# puts "User: #{user}"
# end
# ```
#
# Prints:
#
# ```text
# User: Alice
# User: Charlie
# ```
#
# Accepts an optional *offset* parameter
#
# ```
# %w[Alice Bob Charlie David].each_step(2, offset: 1) do |user|
# puts "User: #{user}"
# end
# ```
#
# Which would print:
#
# ```text
# User: Bob
# User: David
# ```
def each_step(n : Int, *, offset : Int = 0, & : T ->) : Nil
raise ArgumentError.new("Invalid n size: #{n}") if n <= 0
raise ArgumentError.new("Invalid offset size: #{offset}") if offset < 0
offset_mod = offset % n
each_with_index do |elem, i|
yield elem if i >= offset && i % n == offset_mod
end
end

# Iterates over the collection, yielding both the elements and their index.
#
# ```
Expand Down
14 changes: 14 additions & 0 deletions src/iterable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ module Iterable(T)
each.cons_pair
end

# Same as `each.step(n)`.
#
# See also: `Iterator#step`.
def each_step(n : Int)
each.step(n)
end

# Same as `each.skip(offset).step(n)`.
#
# See also: `Iterator#step`.
def each_step(n : Int, *, offset : Int)
each.skip(offset).step(n)
end

# Same as `each.with_index(offset)`.
#
# See also: `Iterator#with_index(offset)`.
Expand Down

0 comments on commit 9e625d5

Please sign in to comment.