Skip to content

Commit

Permalink
Implement no-copy subarray iteration (Implements crystal-lang#3386) (c…
Browse files Browse the repository at this point in the history
…rystal-lang#4584)

* Implement no-copy subarray iteration

* Fix example for Indexable#each(*, within)

* Fix Indexable#each_index(*, start, count) example
  • Loading branch information
cjgajard authored and chris-huxtable committed Jun 6, 2018
1 parent d5282f8 commit 4e4fb51
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 1 deletion.
64 changes: 63 additions & 1 deletion spec/std/indexable_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require "spec"
private class SafeIndexable
include Indexable(Int32)

getter size
property size

def initialize(@size : Int32)
end
Expand Down Expand Up @@ -52,4 +52,66 @@ describe Indexable do
end.should be_nil
is.should eq([0, 1, 2])
end

it "iterates throught a subset of its elements (#3386)" do
indexable = SafeIndexable.new(5)
last_element = nil

return_value = indexable.each(start: 2, count: 3) do |elem|
last_element = elem
end

return_value.should eq(indexable)
last_element.should eq(4)
end

it "iterates until its size (#3386)" do
indexable = SafeIndexable.new(5)
last_element = nil

indexable.each(start: 3, count: 999) do |elem|
last_element = elem
end

last_element.should eq(4)
end

it "iterates until its size, having mutated (#3386)" do
indexable = SafeIndexable.new(10)
last_element = nil

indexable.each(start: 3, count: 999) do |elem|
indexable.size += 1 if elem <= 5
# size is incremented 3 times
last_element = elem
end

# last was 9, but now is 12.
last_element.should eq(12)
end

it "iterates until its size, having mutated (#3386)" do
indexable = SafeIndexable.new(10)
last_element = nil

indexable.each(start: 3, count: 5) do |elem|
indexable.size += 1
last_element = elem
end

# last element iterated is still 7.
last_element.should eq(7)
end

it "iterates within a range of indices (#3386)" do
indexable = SafeIndexable.new(5)
last_element = nil

return_value = indexable.each(within: 2..3) do |elem|
last_element = elem
end

return_value.should eq(indexable)
last_element.should eq(3)
end
end
80 changes: 80 additions & 0 deletions src/indexable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,51 @@ module Indexable(T)
ItemIterator(self, T).new(self)
end

# Calls the given block once for `count` number of elements in `self`
# starting from index `start`, passing each element as a parameter.
#
# Negative indices count backward from the end of the array. (-1 is the
# last element).
#
# Raises `IndexError` if the starting index is out of range.
# Raises `ArgumentError` if `count` is a negative number.
#
# ```
# array = ["a", "b", "c", "d", "e"]
# array.each(start: 1, count: 3) { |x| print x, " -- " }
# ```
#
# produces:
#
# ```text
# b -- c -- d --
# ```
def each(*, start : Int, count : Int)
each_index(start: start, count: count) do |i|
yield unsafe_at(i)
end
end

# Calls the given block once for all elements at indices within the given
# `range`, passing each element as a parameter.
#
# Raises `IndexError` if the starting index is out of range.
#
# ```
# array = ["a", "b", "c", "d", "e"]
# array.each(within: 1..3) { |x| print x, " -- " }
# ```
#
# produces:
#
# ```text
# b -- c -- d --
# ```
def each(*, within range : Range(Int, Int))
start, count = Indexable.range_to_index_and_count(range, size)
each(start: start, count: count) { |element| yield element }
end

# Calls the given block once for each index in `self`, passing that
# index as a parameter.
#
Expand Down Expand Up @@ -201,6 +246,41 @@ module Indexable(T)
IndexIterator.new(self)
end

# Calls the given block once for `count` number of indices in `self`
# starting from index `start`, passing each index as a parameter.
#
# Negative indices count backward from the end of the array. (-1 is the
# last element).
#
# Raises `IndexError` if the starting index is out of range.
# Raises `ArgumentError` if `count` is a negative number.
#
# ```
# array = ["a", "b", "c", "d", "e"]
# array.each_index(start: -3, count: 2) { |x| print x, " -- " }
# ```
#
# produces:
#
# ```text
# 2 -- 3 --
# ```
def each_index(*, start : Int, count : Int)
raise ArgumentError.new "negative count: #{count}" if count < 0

start += size if start < 0
raise IndexError.new unless 0 <= start <= size

i = start
# `count` and size comparison must be done every iteration because
# `self` can mutate in the block.
while i < Math.min(start + count, size)
yield i
i += 1
end
self
end

# Returns `true` if `self` is empty, `false` otherwise.
#
# ```
Expand Down

0 comments on commit 4e4fb51

Please sign in to comment.