Feature #20080
closedIntroduce #bounds method on Range
Description
Followup Reference: #20027
Update 1/11/24: (based on many wonderful suggestions!)
- Call the method
#bounds
.
first, last = (1..300).bounds # => [1, 300]
first, last = (300..1).bounds # => [300, 1]
first, last = (..300).bounds # => [nil, 300]
first, last = (1..).bounds # => [1, nil]
- Add
exclude_end?
support so re-hydration of Range works:
b = (1..2).bounds #=> [1,2]
Range.new(*b) #=> 1..2
b = (1...2).bounds #=> [1,2,true]
Range.new(*b) #=> 1...2
I did a better job of outlining use cases in this comment below so I will let that speak for itself: https://bugs.ruby-lang.org/issues/20080#note-3
Update: 2/13/24
Browsing the ruby codebase I noticed that the #as_json
method on Range (when you require 'json/add/range'
) does something similar to what the #bounds
method we are describing is doing: https://github.com/ruby/ruby/blob/master/ext/json/lib/json/add/range.rb#L34
{
JSON.create_id => self.class.name,
'a' => [ first, last, exclude_end? ]
}
This tells me we are on the right track. Though the difference here is that exclude_end?
is always included. I am thinking the #bounds
should maybe always be including the exclude_end?
piece rather than only include it if it's true
. I am inclined to suggest always including the exclude_end?
regardless of if it is true or false to avoid confusion or people ever calling #last
on the #bounds
results thinking it is the #last
value where it could be the #last
or #exclude_end?
value depending on the type or range. This would still satisfy all of the use cases outlined in this comment: https://bugs.ruby-lang.org/issues/20080#note-3
Therefore I think #bounds
should just always return: [ first, last, exclude_end? ]
Original Proposal:
This feature request is to implement a method called #begin_and_end
on Range
that returns an array of the first and last value stored in a range:
(1..300).begin_and_end #=> [1, 300]
first, last = (300..1).begin_and_end
first #=> 300
last #=> 1
I believe this would be a great addition to Ranges as they are often used to pass around a single object used to hold endpoints, and this allows easier retrieval of those endpoints.
This would allow easier deconstruction into start and end values using array deconstruction as well as a simpler way to serialize to a more primitive object such as an array for database storage.
This implementation was suggested by @mame (Yusuke Endoh) in my initial feature suggestion regarding range deconstruction: https://bugs.ruby-lang.org/issues/20027
This implementation would work similar to how #minmax
works where it returns an array of two numbers, however the difference is that #minmax
doesn't work with reverse ranges as @Dan0042 (Daniel DeLorme) pointed out in the link above:
(1..42).minmax #=> [1, 42]
(42..1).minmax #=> [nil, nil]