Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve documentation of Array#[Range] #10243

Merged
78 changes: 55 additions & 23 deletions src/array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -581,45 +581,77 @@ class Array(T)

# Returns all elements that are within the given range.
#
# Negative indices count backward from the end of the array (-1 is the last
# element). Additionally, an empty array is returned when the starting index
# for an element range is at the end of the array.
#
# Raises `IndexError` if the range's start is out of range.
# The first element in the returned array is `self[range.begin]` followed
# by the next elements up to index `range.end` (or `self[range.end - 1]` if
# the range is exclusive).
# If there are fewer elements in `self`, the returned array is shorter than
# `range.size`.
#
# ```
# a = ["a", "b", "c", "d", "e"]
# a[1..3] # => ["b", "c", "d"]
# a[4..7] # => ["e"]
# a[6..10] # raise IndexError
# a[5..10] # => []
# a[-2...-1] # => ["d"]
# a[2..] # => ["c", "d", "e"]
# ```
def [](range : Range)
# a[1..3] # => ["b", "c", "d"]
# # range.end > array.size
# a[3..7] # => ["d", "e"]
# ```
#
# Open ended ranges are clamped at the start and end of the array, respectively.
#
# ```
# # open ended ranges
# a[2..] # => ["c", "d", "e"]
# a[..2] # => ["a", "b", "c"]
# ```
#
# Negative range values are added to `self.size`, thus they are treated as
# indices counting from the end of the array, `-1` designating the last element.
#
# ```
# # negative indices, both ranges are equivalent for `a`
# a[1..3] # => ["b", "c", "d"]
# a[-4..-2] # => ["b", "c", "d"]
# # Mixing negative and positive indices, both ranges are equivalent for `a`
# a[1..-2] # => ["b", "c", "d"]
# a[-4..3] # => ["b", "c", "d"]
# ```
#
# Raises `IndexError` if the start index is out of range (`range.begin >
# self.size || range.begin < -self.size`). If `range.begin == self.size` an
# empty array is returned. If `range.begin > range.end`, an empty array is
# returned.
#
# ```
# # range.begin > array.size
# a[6..10] # raise IndexError
# # range.begin == array.size
# a[5..10] # => []
# # range.begin > range.end
# a[3..1] # => []
# a[-2..-4] # => []
# a[-2..1] # => []
# a[3..-4] # => []
# ```
def [](range : Range) : self
self[*Indexable.range_to_index_and_count(range, size) || raise IndexError.new]
end

# Like `#[Range]`, but returns `nil` if the range's start is out of range.
# Like `#[](Range)`, but returns `nil` if `range.begin` is out of range.
#
# ```
# a = ["a", "b", "c", "d", "e"]
# a[6..10]? # => nil
# a[6..]? # => nil
# ```
def []?(range : Range)
def []?(range : Range) : self | Nil
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
self[*Indexable.range_to_index_and_count(range, size) || return nil]?
end

# Returns count or less (if there aren't enough) elements starting at the
# given start index.
straight-shoota marked this conversation as resolved.
Show resolved Hide resolved
#
# Negative indices count backward from the end of the array (-1 is the last
# element). Additionally, an empty array is returned when the starting index
# for an element range is at the end of the array.
#
# Raises `IndexError` if the *start* index is out of range.
# Negative *start* is added to `self.size`, thus it's treated as
# index counting from the end of the array, `-1` designating the last element.
#
# Raises `IndexError` if *start* index is out of bounds.
# Raises `ArgumentError` if *count* is negative.
#
# ```
Expand All @@ -629,12 +661,12 @@ class Array(T)
# a[5, 1] # => []
# a[6, 1] # raises IndexError
# ```
def [](start : Int, count : Int)
def [](start : Int, count : Int) : self
self[start, count]? || raise IndexError.new
end

# Like `#[Int, Int]` but returns `nil` if the *start* index is out of range.
def []?(start : Int, count : Int)
# Like `#[](Int, Int)` but returns `nil` if the *start* index is out of range.
def []?(start : Int, count : Int) : self | Nil
raise ArgumentError.new "Negative count: #{count}" if count < 0
return Array(T).new if start == size

Expand Down
4 changes: 2 additions & 2 deletions src/regex/match_data.cr
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class Regex
self[*Indexable.range_to_index_and_count(range, size) || raise IndexError.new]
end

# Like `#[Range]`, but returns `nil` if the range's start is out of range.
# Like `#[](Range)`, but returns `nil` if the range's start is out of range.
def []?(range : Range)
self[*Indexable.range_to_index_and_count(range, size) || raise IndexError.new]?
end
Expand All @@ -218,7 +218,7 @@ class Regex
self[start, count]? || raise IndexError.new
end

# Like `#[Int, Int]` but returns `nil` if the *start* index is out of range.
# Like `#[](Int, Int)` but returns `nil` if the *start* index is out of range.
def []?(start : Int, count : Int)
raise ArgumentError.new "Negative count: #{count}" if count < 0
return Array(String).new if start == size
Expand Down
15 changes: 14 additions & 1 deletion src/slice.cr
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,19 @@ struct Slice(T)

# Returns a new slice with the elements in the given range.
#
# The first element in the returned slice is `self[range.begin]` followed
# by the next elements up to index `range.end` (or `self[range.end - 1]` if
# the range is exclusive).
# If there are fewer elements in `self`, the returned slice is shorter than
# `range.size`.
#
# ```
# a = Slice["a", "b", "c", "d", "e"]
# a[1..3] # => Slice["b", "c", "d"]
# # range.end > array.size
# a[3..7] # => Slice["d", "e"]
# ```
#
# Negative indices count backward from the end of the slice (`-1` is the last
# element). Additionally, an empty slice is returned when the starting index
# for an element range is at the end of the slice.
Expand All @@ -264,7 +277,7 @@ struct Slice(T)
# slice[1..3] # => Slice[11, 12, 13]
# slice[1..33] # raises IndexError
# ```
def [](range : Range)
def [](range : Range) : self
start, count = Indexable.range_to_index_and_count(range, size) || raise IndexError.new
self[start, count]
end
Expand Down
73 changes: 54 additions & 19 deletions src/string.cr
Original file line number Diff line number Diff line change
Expand Up @@ -746,47 +746,82 @@ class String
char_at(index) { raise IndexError.new }
end

# Returns a substring by using a Range's *begin* and *end*
# as character indices. Indices can be negative to start
# counting from the end of the string.
# Returns the substring indicated by *range* as span of character indices.
#
# Raises `IndexError` if the range's start is out of bounds.
# The substring ranges from `self[range.begin]` to `self[range.end]`
# (or `self[range.end - 1]` if the range is exclusive). It can be smaller than
# `range.size` if the end index is larger than `self.size`.
#
# ```
# "hello"[0..2] # => "hel"
# "hello"[0...2] # => "he"
# "hello"[1..-1] # => "ello"
# "hello"[1...-1] # => "ell"
# "hello"[6..7] # raises IndexError
# s = "abcde"
# s[1..3] # => "bcd"
# # range.end > s.size
# s[3..7] # => "de"
# ```
def [](range : Range)
#
# Open ended ranges are clamped at the start and end of `self`, respectively.
#
# ```
# # open ended ranges
# s[2..] # => "cde"
# s[..2] # => "abc"
# ```
#
# Negative range values are added to `self.size`, thus they are treated as
# character indices counting from the end, `-1` designating the last character.
#
# ```
# # negative indices, both ranges are equivalent for `s`
# s[1..3] # => "bcd"
# s[-4..-2] # => "bcd"
# # Mixing negative and positive indices, both ranges are equivalent for `s`
# s[1..-2] # => "bcd"
# s[-4..3] # => "bcd"
# ```
#
# Raises `IndexError` if the start index it out of range (`range.begin >
# self.size || range.begin < -self.size). If `range.begin == self.size` an
# empty string is returned. If `range.begin > range.end`, an empty string is
# returned.
#
# ```
# # range.begin > array.size
# s[6..10] # raise IndexError
# # range.begin == s.size
# s[5..10] # => ""
# # range.begin > range.end
# s[3..1] # => ""
# s[-2..-4] # => ""
# s[-2..1] # => ""
# s[3..-4] # => ""
# ```
def [](range : Range) : String
self[*Indexable.range_to_index_and_count(range, size) || raise IndexError.new]
end

# Like `#[Range]`, but returns `nil` if the range's start is out of bounds.
# Like `#[](Range)`, but returns `nil` if `range.begin` is out of range.
#
# ```
# "hello"[6..7]? # => nil
# "hello"[6..]? # => nil
# ```
def []?(range : Range)
def []?(range : Range) : String?
self[*Indexable.range_to_index_and_count(range, size) || return nil]?
end

# Returns a substring starting from the *start* character of size *count*.
#
# *start* can can be negative to start counting
# from the end of the string.
#
# Raises `IndexError` if the *start* index is out of bounds.
# Negative *start* is added to `self.size`, thus it's treated as a character
# index counting from the end, `-1` designating the last character.
#
# Raises `IndexError` if *start* index is out of bounds.
# Raises `ArgumentError` if *count* is negative.
def [](start : Int, count : Int)
def [](start : Int, count : Int) : String
self[start, count]? || raise IndexError.new
end

# Like `#[Int, Int]` but returns `nil` if the *start* index is out of bounds.
def []?(start : Int, count : Int)
# Like `#[](Int, Int)` but returns `nil` if the *start* index is out of bounds.
def []?(start : Int, count : Int) : String?
raise ArgumentError.new "Negative count: #{count}" if count < 0
return byte_slice?(start, count) if single_byte_optimizable?

Expand Down