Skip to content

Commit

Permalink
Iterator/Iterable: Allow #cons to accept a Deque to reuse too.
Browse files Browse the repository at this point in the history
Deques can be more performant. Quite dramatically so if each_cons is
given large n:s.
  • Loading branch information
yxhuvud committed Jan 10, 2019
1 parent 4107978 commit 20177ff
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 17 deletions.
9 changes: 9 additions & 0 deletions spec/std/enumerable_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ describe "Enumerable" do
a.should be(reuse)
end

it "returns each_cons iterator with reuse = deque" do
reuse = Deque(Int32).new
iter = [1, 2, 3, 4, 5].each_cons(3, reuse: reuse)

a = iter.next
a.should eq(Deque{1, 2, 3})
a.should be(reuse)
end

it "yields running pairs with reuse = true" do
array = [] of Array(Int32)
object_ids = Set(UInt64).new
Expand Down
72 changes: 72 additions & 0 deletions spec/std/iterator_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,78 @@ describe Iterator do
iter.rewind
iter.next.should eq([1, 2, 3])
end

describe "reuse" do
it "reuse as nil" do
iter = (1..5).each.cons(3, reuse: nil)
first = iter.next
first.should eq([1, 2, 3])
second = iter.next
second.should eq([2, 3, 4])
first.should_not be(second)
iter.next.should eq([3, 4, 5])
iter.next.should be_a(Iterator::Stop)

iter.rewind
iter.next.should eq([1, 2, 3])
iter.next.should_not be(first)
end

it "reuse as Bool" do
iter = (1..5).each.cons(3, reuse: true)
first = iter.next
first.should eq([1, 2, 3])
second = iter.next
second.should eq([2, 3, 4])
first.should be(second)
iter.next.should eq([3, 4, 5])
iter.next.should be_a(Iterator::Stop)

iter.rewind
iter.next.should eq([1, 2, 3])
iter.next.should be(first)
end

it "reuse as Array" do
reuse = [] of Int32
iter = (1..5).each.cons(3, reuse: reuse)
value = iter.next
value.should be(reuse)
value.should eq([1, 2, 3])
value = iter.next
value.should be(reuse)
value.should eq([2, 3, 4])
value = iter.next
value.should be(reuse)
value.should eq([3, 4, 5])
iter.next.should be_a(Iterator::Stop)

iter.rewind
value = iter.next
value.should be(reuse)
value.should eq([1, 2, 3])
end

it "reuse as deque" do
reuse = Deque(Int32).new
iter = (1..5).each.cons(3, reuse: reuse)
value = iter.next
value.should be(reuse)
value.should eq(Deque{1, 2, 3})
value = iter.next
value.should be(reuse)
value.should eq(Deque{2, 3, 4})
value = iter.next
value.should be(reuse)
value.should eq(Deque{3, 4, 5})
iter.next.should be_a(Iterator::Stop)

iter.rewind
value = iter.next
value.should be(reuse)
value.should eq(Deque{1, 2, 3})
end
end
end

describe "cycle" do
Expand Down
30 changes: 13 additions & 17 deletions src/iterator.cr
Original file line number Diff line number Diff line change
Expand Up @@ -293,34 +293,30 @@ module Iterator(T)
# iter.next # => Iterator::Stop::INSTANCE
# ```
#
# By default, a new array is created and yielded for each consecutive when invoking `next`.
# By default, a new array is created and returned for each consecutive call of `next`.
# * If *reuse* is given, the array can be reused
# * If *reuse* is an `Array`, this array will be reused
# * If *reuse* is truthy, the method will create a new array and reuse it.
# * If *reuse* is `true`, the method will create a new array and reuse it.
# * If *reuse* is an instance of `Array`, `Deque` or a similar collection type (implementing `#<<`, `#shift` and `#size`) it will be used.
# * If *reuse* is falsey, the array will not be reused.
#
# This can be used to prevent many memory allocations when each slice of
# interest is to be used in a read-only fashion.
def cons(n : Int, reuse = false)
raise ArgumentError.new "Invalid cons size: #{n}" if n <= 0
Cons(typeof(self), T, typeof(n)).new(self, n, reuse)
if reuse.nil? || reuse.is_a?(Bool)
Cons(typeof(self), T, typeof(n), Array(T)).new(self, n, Array(T).new(n), reuse)
else
Cons(typeof(self), T, typeof(n), typeof(reuse)).new(self, n, reuse, reuse)
end
end

private struct Cons(I, T, N)
private struct Cons(I, T, N, V)
include Iterator(Array(T))
include IteratorWrapper

def initialize(@iterator : I, @n : N, reuse)
if reuse
if reuse.is_a?(Array)
@values = reuse
else
@values = Array(T).new(@n)
end
@reuse = true
else
@values = Array(T).new(@n)
@reuse = false
end
def initialize(@iterator : I, @n : N, values : V, reuse)
@values = values
@reuse = !!reuse
end

def next
Expand Down

0 comments on commit 20177ff

Please sign in to comment.