From 881eb819516a2618eaae3986dfad5b44c5e8a8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Fri, 4 Jun 2021 11:18:15 +0200 Subject: [PATCH] Improve documentation of Array#[Range] (#10243) Co-authored-by: Quinton Miller --- src/array.cr | 78 +++++++++++++++++++++++++++++------------ src/regex/match_data.cr | 4 +-- src/slice.cr | 15 +++++++- src/string.cr | 73 ++++++++++++++++++++++++++++---------- 4 files changed, 125 insertions(+), 45 deletions(-) diff --git a/src/array.cr b/src/array.cr index 84600c1314d4..e5c8544e2fa5 100644 --- a/src/array.cr +++ b/src/array.cr @@ -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. # # ``` @@ -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 diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index c7657434e941..0bd6a089facf 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -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 @@ -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 diff --git a/src/slice.cr b/src/slice.cr index 936e7d3a66e8..3e0a2c828f5f 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -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. @@ -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 diff --git a/src/string.cr b/src/string.cr index a8e30781cbd3..2d31c0dbfaad 100644 --- a/src/string.cr +++ b/src/string.cr @@ -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?