How does ruby handle array range accessing?
This is a known ugly odd corner. Take a look at the examples in rdoc for Array#slice.
This specific issue is listed as a "special case"
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
If the start is exactly one item beyond the end of the array, then it will return []
, an empty array. If the start is beyond that, nil
. It's documented, though I'm not sure of the reason for it.
How to select array elements in a given range in Ruby?
You can use ranges in the array subscript:
arr[100..200]
Why does a Ruby array allow access to invalid range index?
Ruby is not a static language, forcing you to pre-declare the size of the array. It expands arrays as you assign to a particular element.
Normally we'd append to the end of the array:
array = [] # => []
array << 1 # => [1]
array += [2] # => [1, 2]
array.push(3) # => [1, 2, 3]
Or push onto the front of it:
array.unshift(0) # => [0, 1, 2, 3]
to add elements, which keeps the array accumulating the values without gaps.
We can do it randomly too, which can be useful:
array[10] = 10 # => 10
array # => [0, 1, 2, 3, nil, nil, nil, nil, nil, nil, 10]
And that's what you've encountered.
You can predefine the array to a size, but it remains dynamic:
ary = Array.new(10, nil) # => [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
ary[0] = 0 # => 0
ary[10] = 10 # => 10
ary # => [0, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10]
ary[12]=12 # => 12
ary # => [0, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, 12]
How do you check an array for a range in Ruby?
Edit 2: This is my absolutely final solution:
require 'set'
STRAIGHTS = ['A',*2..9,'T','J','Q','K','A'].each_cons(5).map(&:to_set)
#=> [#<Set: {"A", 2, 3, 4, 5}>, #<Set: {2, 3, 4, 5, 6}>,
# ...#<Set: {9, "T", "J", "Q", "K"}>, #<Set: {"T", "J", "Q", "K", "A"}>]
def straight?(hand)
STRAIGHTS.include?(hand.to_set)
end
STRAIGHTS.include?([6,3,4,5,2].to_set)
# STRAIGHTS.include?(#<Set: {6, 3, 4, 5, 2}>)
#=> true
straight?([6,5,4,3,2]) #=> true
straight?(["T","J","Q","K","A"]) #=> true
straight?(["A","K","Q","J","T"]) #=> true
straight?([2,3,4,5,"A"]) #=> true
straight?([6,7,8,9,"J"]) #=> false
straight?(["J",7,8,9,"T"]) #=> false
Edit 1: @mudasobwa upset the apple cart by pointing out that 'A',2,3,4,5
is a valid straight. I believe I've fixed my answer. (I trust he's not going to tell me that 'K','A',2,3,4
is also valid.)
I would suggest the following:
CARDS = [2, 3, 4, 5, 6, 7, 8, 9, "T", "J", "Q", "K", "A"]
STRAIGHTS = CARDS.each_cons(5).to_a
#=>[[2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8],
# [5, 6, 7, 8, 9], [6, 7, 8, 9, "T"], [7, 8, 9, "T", "J"],
# [8, 9, "T", "J", "Q"], [9, "T", "J", "Q", "K"],
# ["T", "J", "Q", "K", "A"]]
def straight?(hand)
(hand.map {|c| CARDS.index(c)}.sort == [0,1,2,3,12]) ||
STRAIGHTS.include?(hand.sort {|a,b| CARDS.index(a) <=> CARDS.index(b)})
end
Array of indexes to array of ranges
(New and improved. Stays fresh in your refrigerator for up to two weeks!):
a = [1, 2, 3, 10, 11, 20, 20, 4]
ranges = a.sort.uniq.inject([]) do |spans, n|
if spans.empty? || spans.last.last != n - 1
spans + [n..n]
else
spans[0..-2] + [spans.last.first..n]
end
end
p ranges # [1..4, 10..11, 20..20]
How do I summarize array of integers as an array of ranges?
Functional approach using Enumerable#chunk:
ranges = [1, 2, 4, 5, 6, 7, 9, 13]
.enum_for(:chunk) # .chunk for Ruby >= 2.4
.with_index { |x, idx| x - idx }
.map { |_diff, group| [group.first, group.last] }
#=> [[1, 2], [4, 7], [9, 9], [13, 13]]
How it works: once indexed, consecutive elements in the array have the same x - idx
, so we use that value to chunk (grouping of consecutive items) the input array. Finally we just need to take the first and last elements of each group to build the pairs.
Efficiency of Arrays vs Ranges in Ruby
TL;DR
Ranges are generally faster and more memory-efficient than reifying Arrays. However, specific use cases may vary.
If in doubt, benchmark. You can use irb's relatively new measure command, or use the Benchmark module to compare and contrast different approaches. In general, reifying a Range as an Array takes more memory and is slower than comparing against a Range (or even a small Array of Range objects), but unless you loop over this code a lot this seems like a premature optimization.
Benchmarks
Using Ruby 3.1.0, the Range approach is around 3,655.77% faster on my system. For example:
require 'benchmark'
n = 100_000
Benchmark.bmbm do
_1.report("Range") do
n.times do
client_error = [200..299, 400..499]
client_error.include? 404
end
end
_1.report("Array") do
n.times do
client_error = [*(200..299), *(400..499)]
client_error.include? 404
end
end
end
Rehearsal -----------------------------------------
Range 0.022570 0.000107 0.022677 ( 0.022832)
Array 0.707742 0.041499 0.749241 ( 0.750012)
-------------------------------- total: 0.771918sec
user system total real
Range 0.020184 0.000043 0.020227 ( 0.020245)
Array 0.701911 0.037541 0.739452 ( 0.740037)
While the overall total times are better with Jruby and TruffleRuby, the performance differences between the approaches are only about 3-7x faster with Ranges. Meanwhile, Ruby 3.0.1 shows an approximate 37x speed improvement using a non-reified Range rather than an Array, so the Range approach is the clear winner here either way.
Your specific values will vary based on system specs, system load, and Ruby version and engine. For smaller values of n, I can't imagine it will make any practical difference, but you should definitely benchmark against your own systems to determine if the juice is worth the squeeze.
Ruby - Arrays, bounds, and raising exceptions
Accessing an array element that's outside the range of existing elements returns nil. That's just the way Ruby works.
You could add the following line before the "puts" to trap that condition...
raise StandardError if index.to_i >= arr.size
Related Topics
Naming Conventions for Boolean Attributes
Factory_Girl + Rspec Doesn't Seem to Roll Back Changes After Each Example
How to Refresh a Page with Turbolinks
Rails Routing: Giving Default Values for Path Helpers
Package Configuration for Libffi Is Not Found in MACos While Installing Travis-Cli
Carrierwave: Create 1 Uploader for Multiple Types of Files
How to Host a Rails Web Application
Result.Credit_Card_Verification Is Returning Nil Even on Error in Braintree
Ruby, How to Add a Param to an Url That You Don't Know If It Has Any Other Param Already
Get Available Diskspace in Ruby
What Are Factory_Girl Transient Attributes? Why Would I Use One
How to Store a Ruby Array into a File
Ruby: How to Join Elements of an Array Together with a Prefix
How Does Rake Db::Migrate Actually Work