Skip to content

Commit

Permalink
Improve documentation of Array#[Range] (#10243)
Browse files Browse the repository at this point in the history
Co-authored-by: Quinton Miller <[email protected]>
  • Loading branch information
straight-shoota and HertzDevil authored Jun 4, 2021
1 parent 1f2d90c commit 881eb81
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 45 deletions.
78 changes: 55 additions & 23 deletions src/array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -588,45 +588,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
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.
#
# 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 @@ -636,12 +668,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 @@ -767,47 +767,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

0 comments on commit 881eb81

Please sign in to comment.