Rand (Range) - does not mean converting Range to Integer

Next up is How to create a random time between a range .

Kernel#rand works with the Time range:

 require 'time' rand(Time.parse('9 am')..Time.parse('11:30 am')) 

But when I tried with a custom class, I ended up with an error:

`rand ': implicit conversion of Range to Integer (TypeError)

 class Int include Comparable attr_reader :num def initialize(num) @num = num end def succ Int.new(num + 1) end def <=>(other) num <=> other.num end def to_s "Int(#{num})" end def to_int @num end alias_method :inspect, :to_s end puts rand(Int.new(1)..Int.new(3)) 

Why? What am I missing in a custom class? Is it possible to use such a custom class in rand(Range) ?

+5
source share
3 answers

I don't know any documentation about exactly what Kernel#rand expects from the Range argument, but can we see what happens by overriding respond_to? in your class and then watching how things fall apart:

 def respond_to?(m) puts "They want us to support #{m}" super end 

Doing this tells us that rand wants to call the #- and #+ methods on your Int instances. This makes sense, considering that rand(a..b) designed to work with integers.

Thus, we throw fast dirty implementations of addition and subtraction:

 def -(other) self.class.new(to_int - other.to_int) end def +(other) self.class.new(to_int + other.to_int) end 

and we start getting rand Int from our calls to rand .


I don’t know where (or if) this is documented, so you have to excuse me a little wave of the hand. I usually spend some time using the Ruby source code to answer this question, but now I don't have enough time.

+4
source

To add a little more to @ mu-is-too-short, I checked the source of Random#rand , and the following implementation logic for rand(Range) :

  • Get the start, end and vmax from the Range object ( call range_values ), where vmax calculated as ( call id_minus ):

     vmax = end - begin 

    vmax will be used as the upper bound on random number generation later.

    This requires the user class to have the - method.

  • Create a random number based on vmax type:

    • If it is not a Float and can be forced to bind to an Integer ( rb_check_to_int ), generate a random Integer less than vmax .

      In this case, the method should either return an Integer or an object that responds to the to_int method.

    • If it is Numeric and can be converted to Float with to_f , ( rb_check_to_float ), generate a random Float number less than vmax .

      In this case, the - method should return a Numeric number, which can be converted to Float using the to_f method.

  • Add a random number to begin to get the result ( call id_add ).

    This requires the user class to have a + method that takes the result of the random number generated in step 2 (either Integer or Float ) and returns the final result for rand .

+3
source

I believe this error is due to the fact that you are trying to use rand () for objects of your custom class.

 `rand': no implicit conversion of Range into Integer (TypeError) 

This error message clearly states that ruby ​​was unable to convert your range to an integer. Based on your code snippet, do the following work and maybe you're looking.

 puts rand(Int.new(1).to_int..Int.new(3).to_int) 
0
source

Source: https://habr.com/ru/post/1274745/


All Articles