3

I have an array of a reasonable size,

a = ("a".."z").to_a

and I want to use a certain default number that would (practically) always return nil when used to call a value as an index.

a[some_default_index] # => nil

For a fixed a, I can set a fixed number equal to or larger than a's size as the default number, but a varies.

I tried Float::NAN and Float::Infinity, which did not work as I intended.

a[Float::NAN] # => out of range error
a[Float::Infinity] # => out of range error

What number can I use as an index?

sawa
  • 165,429
  • 45
  • 277
  • 381

2 Answers2

1

Try this

max_index = 2**30 - 1
a[max_index] # => nil 

The error message gives the hint RangeError: bignum too big to convert into `long'

That is, the index must fit into long value in the underlying machine-level representation of Ruby. On a 32-bit system that would be 2**31 - 1 and 2**63 - 1 on a 64-bit system.

However, these values are to big to be represented as a Fixnum in Ruby so if you want to take advantage of the immediate speedup of fixnums best use their maximum value instead. Ruby uses tag pointers to represent Fixnum integers, hence they are one bit smaller that native integers and thus 2**30 - 1 for a 32-bit system and 2**62 - 1 for a 64-bit system.

akuhn
  • 27,477
  • 2
  • 76
  • 91
  • That creates a `Bignum` on 32-bit system. Ruby uses tag pointers hence integers are one bit smaller. – akuhn Jan 24 '17 at 17:24
1

Largest correct index

Here's an adapted version of @AndrewGrimm's excellent answer :

a = ("a".."z").to_a

start = Time.now
largest_correct_index = 1
smallest_incorrect_index = nil

until smallest_incorrect_index == largest_correct_index + 1
  if smallest_incorrect_index.nil?
    next_number_to_try = largest_correct_index * 1000
  else
    next_number_to_try = (smallest_incorrect_index + largest_correct_index) / 2 # Geometric mean would be more efficient, but more risky
  end

  if next_number_to_try <= largest_correct_index ||
       smallest_incorrect_index && next_number_to_try >= smallest_incorrect_index
    raise "Can't happen case"
  end

  begin
    a[next_number_to_try]
    largest_correct_index = next_number_to_try
  rescue RangeError
    smallest_incorrect_index = next_number_to_try
  end
end

finish = Time.now
puts "The largest correct index is #{largest_correct_index}"
puts "The smallest incorrect index is #{smallest_incorrect_index}"
puts "Calculation took #{finish - start} seconds"

On 32-bit Ruby, it returned 2**31-1:

The largest correct index is 2147483647
The smallest incorrect index is 2147483648
Calculation took 0.0 seconds

On 64-bit Ruby and JRuby, it returned 2**63-1:

The largest correct index is 9223372036854775807
The smallest incorrect index is 9223372036854775808
Calculation took 0.000250378 seconds

So it looks like @akuhn's answer should be good enough in most cases.

Alternative

Depending on your needs, you could also use a hash :

a = ('a'..'z').to_a
# ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

h = ('a'..'z').map.with_index { |l, i| [i, l] }.to_h
# {0=>"a", 1=>"b", 2=>"c", 3=>"d", 4=>"e", 5=>"f", 6=>"g", 7=>"h", 8=>"i", 9=>"j", 10=>"k", 11=>"l", 12=>"m", 13=>"n", 14=>"o", 15=>"p", 16=>"q", 17=>"r", 18=>"s", 19=>"t", 20=>"u", 21=>"v", 22=>"w", 23=>"x", 24=>"y", 25=>"z"}

require 'fruity'

compare do
  _array { a[rand(26)] }
  _hash  { h[rand(26)] }
end

It doesn't seem to impact performances negatively :

Running each test 16384 times. Test will take about 1 second.
_array is similar to _hash

All those :

h[-1]
h[:default]
h[Float::INFINITY]
h[2**1024-1]

would return nil.

Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124