Why doesn't `Range#cover?` raise an exception when comparison fails? - ruby

Given that Time objects cannot be compared with Fixnum without explicit casting:
0 <= Time.now # => ArgumentError: comparison of Fixnum with Time failed
Time.now <= 10000000000 # => ArgumentError: comparison of Time with 10000000000 failed
and what the documentation for Range#cover? says,
cover?(obj) → true or false
Returns true if obj is between the begin and end of the range.
This tests begin <= obj <= end when exclude_end? is false and begin <= obj < end when exclude_end? is true.
I expect:
(0...10000000000).cover?(Time.now) # => false
to raise an exception rather than silently return false. Why doesn't it raise an exception?
It is understandable that, with explicit casting, the comparison works:
(0...10000000000).cover?(Time.now.to_i) # => true

The doc doesn't mention an implementation detail. range_cover is implemented in terms of r_less (via r_cover_p). And r_less comment says:
/* compares _a_ and _b_ and returns:
* < 0: a < b
* = 0: a = b
* > 0: a > b or non-comparable
*/
Here is the source of r_cover_p:
static VALUE
r_cover_p(VALUE range, VALUE beg, VALUE end, VALUE val)
{
if (r_less(beg, val) <= 0) {
int excl = EXCL(range);
if (r_less(val, end) <= -excl)
return Qtrue;
}
return Qfalse;
}
As we can see, a positive number returned from either of r_less invocations will result in a Qfalse.
Now, the reason why the doc doesn't mention it, I think, is to keep it light. Normally (99.9999% of cases), you're supposed to compare comparable things, right? And in the odd case you don't, you still get a correct answer ("this Time does not belong to this range of integers").

I am pretty sure both .include? and .cover? uses the case quality operator (===) so the value you get is the same as:
p Time.now === 1000 #=> false

Related

How can I avoid error when setting elsif range condition?

def Summer
#summer = true
puts "Your fruit are ripe for the picking."
if #tree_age == 1..5 && #tree_age > 0
#oranges = 5
elsif #tree_age == 6..15
#oranges = 20
else
#oranges = 50
end
end
I'm trying to ensure a tree between a certain age range gives x oranges, however I'm stuck with the following error referring to my elsif statement:
Orange_tree.rb:14: warning: integer literal in conditional range
I have also tried using an if greater than && less than conditional statement, can somebody please explain what this error means, and how to reach my solution.
You have a few problems:
You'll want to put your ranges in parenthesis when other operators or methods are nearby. Your current error comes from Ruby parsing elsif #tree_age == 6..15 differently than you expect - it's treating it as (1 == 6)..15, and false..15 obviously doesn't make any sense.
To test a number is within a range, use (1..5) === num, not num == (1..5). Range#=== is defined to test that the Range includes the right hand side, while Fixnum#== and Fixnum#=== both just test that the right hand side is numerically equivalent.
You don't need to test #tree_age > 0. You're already testing that it's in 1..5.
You could also consider a case statement for this, which can be a bit easier to read. case does its comparisons using ===.
#oranges = case #tree_age
when 1..5 then 5
when 6..15 then 20
else 50
end
You should use include? instead of == to determine if the given number is within the range:
def Summer
#summer = true
puts "Your fruit are ripe for the picking."
if (1..5).include?(#tree_age) && #tree_age > 0
#oranges = 5
elsif (6..15).include? #tree_age
#oranges = 20
else
#oranges = 50
end
end
==:
Returns true only if obj is a Range, has equivalent begin and end
items (by comparing them with ==), and has the same exclude_end?
setting as the range.
Which is obviously not the case.
The problem is with the lines that say == with a range.
if ( 10 == 1..11) # throws integer literal in conditional range warning
puts "true"
end
If you did this instead
if ( 10.between?(1, 11))
puts "true"
end

How to check if a value is included between two other values?

I'm trying to express a condition like this:
if 33.75 < degree <= 56.25
# some code
end
But Ruby gives this error:
undefined method `<=' for true:TrueClass
I'm guessing that one way to do it is something like:
if 33.75 < degree and degree <= 56.25
# code
end
But there is no another, easier way?
Ruby also has between?:
if value.between?(lower, higher)
There are many ways of doing the same things in Ruby.
You can check if value is in the range by use of following methods,
14.between?(10,20) # true
(10..20).member?(14) # true
(10..20).include?(14) # true
But, I would suggest using between than member? or include?. You can find more about it here.
You can express a <= x <= b as (a..b).include? x and a <= x < b as (a...b).include? x.
>> (33.75..56.25).include? 33.9
=> true
>> (33.75..56.25).include? 56.25
=> true
>>
>> (33.75..56.25).include? 56.55
=> false
Unfortunately, there seems no such thing for a < x <= b, a < x < b, ..
UPDATE
You can accomplish using (-56.25...-33.75).include? -degree. But it's hard to read. So I recommend you to use 33.75 < degree and degree <= 56.25.
use between? is the easiest way, I found most answers here didn't mention (ruby doc explanation is hard to understand too), using between? does INCLUDE the min and max value.
for example:
irb(main):001:0> 2.between?(1, 3)
=> true
irb(main):002:0> 3.between?(1, 3)
=> true
irb(main):003:0> 1.between?(1, 3)
=> true
irb(main):004:0> 0.between?(1, 3)
=> false
by the way, ruby doc quote:
between?(min, max) → true or false Returns false if obj <=> min is
less than zero or if anObject <=> max is greater than zero, true
otherwise.
undefined method `<=' for true:TrueClass
means that Ruby is not parsing your if-condition the way you expect it.
Using && and adding parentheses helps!
if (33.75<degree) && (degree<=56.25)
...
end
It's a bad habit to leave out parentheses -- as soon as the expression gets more difficult, you could get a surprising outcome. I've seen this many times in other people's code.
Using and instead of && in Ruby is a very bad idea, see:
https://www.tinfoilsecurity.com/blog/ruby-demystified-and-vs
http://rubyinrails.com/2014/01/30/difference-between-and-and-in-ruby/
You can also use this notation:
(1..5) === 3 # => true
(1..5) === 6 # => false
Adjust value to range?
value = 99
[[value,0].max, 5].min # 5
value = -99
[[value,0].max, 5].min # 0
value = 3
[[value,0].max, 5].min # 3

Ruby: intersection between two ranges

In ruby, given two date ranges, I want the range that represents the intersection of the two date ranges, or nil if no intersection. For example:
(Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
=> Mon, 10 Jan 2011..Sat, 15 Jan 2011
Edit: Should have said that I want it to work for DateTime as well, so interval can be down to mins and secs:
(DateTime.new(2011,1,1,22,45)..Date.new(2011,2,15)) & (Date.new(2011,1,1)..Date.new(2011,2,15))
=> Sat, 01 Jan 2011 22:45:00 +0000..Tue, 15 Feb 2011
require 'date'
class Range
def intersection(other)
return nil if (self.max < other.begin or other.max < self.begin)
[self.begin, other.begin].max..[self.max, other.max].min
end
alias_method :&, :intersection
end
p (Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
#<Date: 2011-01-10 ((2455572j,0s,0n),+0s,2299161j)>..#<Date: 2011-01-15 ((2455577j,0s,0n),+0s,2299161j)>
You can try this to get a range representing intersection
range1 = Date.new(2011,12,1)..Date.new(2011,12,10)
range2 = Date.new(2011,12,4)..Date.new(2011,12,12)
inters = range1.to_a & range2.to_a
intersected_range = inters.min..inters.max
Converting your example:
class Range
def intersection(other)
raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
inters = self.to_a & other.to_a
inters.empty? ? nil : inters.min..inters.max
end
alias_method :&, :intersection
end
I found this: http://www.postal-code.com/binarycode/2009/06/06/better-range-intersection-in-ruby/ which is a pretty good start, but does not work for dates. I've tweaked a bit into this:
class Range
def intersection(other)
raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)
new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil
new_max = self.cover?(other.max) ? other.max : other.cover?(max) ? max : nil
new_min && new_max ? new_min..new_max : nil
end
alias_method :&, :intersection
end
I've omitted the tests, but they are basically the tests from the post above changed for dates. This works for ruby 1.9.2.
Anyone got a better solution?
I baked this solution for ascending ranges, also taking care of the exclude end situations:
intersect_ranges = ->(r1, r2) do
new_end = [r1.end, r2.end].min
new_begin = [r1.begin, r2.begin].max
exclude_end = (r2.exclude_end? && new_end == r2.end) || (r1.exclude_end? && new_end == r1.end)
valid = (new_begin <= new_end && !exclude_end)
valid ||= (new_begin < new_end && exclude_end))
valid ? Range.new(new_begin, new_end, exclude_end) : nil
end
I'm also a bit worried by you guys adding it to the Range class itself, since the behavior of intersecting ranges is not uniformly defined.
(How about intersecting 1...4 and 4...1? Why nil when there is no intersection; we could also say this is an empty range: 1...1 )
You can use overlaps? with Range starting with Rails v3
# For dates, make sure you have the correct format
first_range = first_start.to_date..first_end.to_date
second_range = second_start.to_date..second_end.to_date
intersection = first_range.overlaps?(second_range) # => Boolean
# Example with numbers
(1..7).overlaps?(3..5) # => true
More details in the docs
Try something like this
require 'date'
sample = Date.parse('2011-01-01')
sample1 = Date.parse('2011-01-15')
sample2 = Date.parse('2010-12-19')
sample3 = Date.parse('2011-01-11')
puts (sample..sample1).to_a & (sample2..sample3).to_a
What this will give you is a array of intersection dates!!
I have times as [[start, end], ...] and I want to remove the some time ranges from a each initial time range, here is what I did:
def exclude_intersecting_time_ranges(initial_times, other_times)
initial_times.map { |initial_time|
other_times.each do |other_time|
next unless initial_time
# Other started after initial ended
next if other_time.first >= initial_time.last
# Other ended before initial started
next if other_time.last <= initial_time.first
# if other time started before and ended after after, no hour is counted
if other_time.first <= initial_time.first && other_time.last >= initial_time.last
initial_time = nil
# if other time range is inside initial time range, split in two time ranges
elsif initial_time.first < other_time.first && initial_time.last > other_time.last
initial_times.push([other_time.last, initial_time.last])
initial_time = [initial_time.first, other_time.first]
# if start time of other time range is before initial time range
elsif other_time.first <= initial_time.first
initial_time = [other_time.last, initial_time.last]
# if end time of other time range if after initial time range
elsif other_time.last >= initial_time.last
initial_time = [initial_time.first, other_time.first]
end
end
initial_time
}.compact
end
Since this question is related to How to combine overlapping time ranges (time ranges union), I also wanted to post my finding of the gem range_operators here, because if helped me in the same situation.
I'd transfer them into an array, since arrays know the intersection-operation:
(Date.new(2011,1,1)..Date.new(2011,1,15)).to_a & (Date.new(2011,1,10)..Date.new(2011,2,15)).to_a
Of course this returns an Array. So if you want an Enumerator (Range doesn't seem to be possible since these are not consecutive values anymore) just throw to_enum at the end.

Lychrel numbers

First of all, for those of you, who don't know (or forgot) about Lychrel numbers, here is an entry from Wikipedia: http://en.wikipedia.org/wiki/Lychrel_number.
I want to implement the Lychrel number detector in the range from 0 to 10_000. Here is my solution:
class Integer
# Return a reversed integer number, e.g.:
#
# 1632.reverse #=> 2361
#
def reverse
self.to_s.reverse.to_i
end
# Check, whether given number
# is the Lychrel number or not.
#
def lychrel?(depth=30)
if depth == 0
return true
elsif self == self.reverse and depth != 30 # [1]
return false
end
# In case both statements are false, try
# recursive "reverse and add" again.
(self + self.reverse).lychrel?(depth-1)
end
end
puts (0..10000).find_all(&:lychrel?)
The issue with this code is the depth value [1]. So, basically, depth is a value, that defines how many times we need to proceed through the iteration process, to be sure, that current number is really a Lychrel number. The default value is 30 iterations, but I want to add more latitude, so programmer can specify his own depth through method's parameter. The 30 iterations is perfect for such small range as I need, but if I want to cover all natural numbers, I have to be more agile.
Because of the recursion, that takes a place in Integer#lychrel?, I can't be agile. If I had provided an argument to the lychrel?, there wouldn't have been any changes because of the [1] statement.
So, my question sounds like this: "How do I refactor my method, so it will accept parameters correctly?".
What you currently have is known as tail recursion. This can usually be re-written as a loop to get rid of the recursive call and eliminate the risk of running out of stack space. Try something more like this:
def lychrel?(depth=30)
val = self
first_iteration = true
while depth > 0 do
# Return false if the number has become a palindrome,
# but allow a palindrome as input
if first_iteration
first_iteration = false
else
if val == val.reverse
return false
end
# Perform next iteration
val = (val + val.reverse)
depth = depth - 1
end
return true
end
I don't have Ruby installed on this machine so I can't verify whether that 's 100% correct, but you get the idea. Also, I'm assuming that the purpose of the and depth != 30 bit is to allow a palindrome to be provided as input without immediately returning false.
By looping, you can use a state variable like first_iteration to keep track of whether or not you need to do the val == val.reverse check. With the recursive solution, scoping limitations prevent you from tracking this easily (you'd have to add another function parameter and pass the state variable to each recursive call in turn).
A more clean and ruby-like solution:
class Integer
def reverse
self.to_s.reverse.to_i
end
def lychrel?(depth=50)
n = self
depth.times do |i|
r = n.reverse
return false if i > 0 and n == r
n += r
end
true
end
end
puts (0...10000).find_all(&:lychrel?) #=> 249 numbers
bta's solution with some corrections:
class Integer
def reverse
self.to_s.reverse.to_i
end
def lychrel?(depth=30)
this = self
first_iteration = true
begin
if first_iteration
first_iteration = false
elsif this == this.reverse
return false
end
this += this.reverse
depth -= 1
end while depth > 0
return true
end
end
puts (1..10000).find_all { |num| num.lychrel?(255) }
Not so fast, but it works:
code/practice/ruby% time ruby lychrel.rb > /dev/null
ruby lychrel.rb > /dev/null 1.14s user 0.00s system 99% cpu 1.150 total

Trouble with combined logic in ruby

writing a simple monte carlo simulation of a neutron beam. Having trouble with the geometry logic (whether something is in one environment or another). My issue is that Ruby seems to be processing the conditions sequentially and keeping the first value it comes to.
The code below illustrates this quite nicely:
def checkPosition(*args)
polyCylRad = 2.5
polyCylFr = 15
polyCylB = -2.0
borPolyBoxL = 9.0 / 2
pbCylRad = 3.0
pbBoxL = 10.0 / 2
cdBoxL = 9.5 / 2
position = Array.new
material = String.new
args.each do |item|
position << item.inspect.to_f
end
xSquared = position.at(0) ** 2
ySquared = position.at(1) ** 2
zSquared = position.at(2) ** 2
modX = Math.sqrt(xSquared)
modY = Math.sqrt(ySquared)
modZ = Math.sqrt(zSquared)
puts xSquared
puts Math.sqrt(ySquared + zSquared) <= polyCylRad
puts (position.at(0) >= polyCylB)
puts (position.at(0) <= polyCylFr)
puts (position.at(0) >= polyCylB)and(position.at(0) <= polyCylFr)
puts (position.at(0) <= polyCylFr)and(position.at(0) >= polyCylB)
puts zSquared
polyCylinder = (Math.sqrt(ySquared + zSquared) <= polyCylRad)and((position.at(0) >= polyCylB)and(position.at(0) <= polyCylFr) )
puts polyCylinder
borPolyBox = ((modX <= borPolyBoxL)or(modY < borPolyBoxL)or(modZ <= borPolyBoxL)) and not((modX >= cdBoxL)or(modY >= cdBoxL)or(modZ >= cdBoxL)) and not(Math.sqrt(ySquared + zSquared) <= polyCylRad)
puts borPolyBox
cadmiumShield = ((modX <= cdBoxL)or(modY < cdBoxL)or(modZ <= cdBoxL)) and not((modX >= pbBoxL)or(modY >= pbBoxL)or(modZ >= pbBoxL)) and not(Math.sqrt(ySquared + zSquared) <= polyCylRad)
puts cadmiumShield
leadShield = ( ((modX <= pbBoxL)or(modY <= pbBoxL)or(modZ <= pbBoxL)) or ((position.at(0) <= ployCylFr)and(Math.sqrt(ySquared + zSquared) <= pbCylRad)) ) and not(Math.sqrt(ySquared + zSquared) <= polyCylRad)
puts leadShield
if (polyCylinder) : material = "poly"
elsif(borPolyBox) : material = "borPoly"
elsif(cadmiumSheild) : material = "cd"
elsif(leadSheild) : material = "pb"
elsif(material == nil) : position = Array.new
end
thisEnvironment = Array.new
thisEnvironment << position << material
puts thisEnvironment.at(0)
puts thisEnvironment.at(1)
end
checkPosition(40, 0, 0)
call the code whatever you want, but give it *args as an argument (I am lazy and may want to add more args in the future) then call it with 3 floats, wrt the geometry set up in the logic and you'll see what I mean.
My question is: how do I get it to work like it should (ie evaluating the logic correctly) without a whole bunch of nested if's? (which is what I am about to remake, however it is a nightmare to read and memory is cheap.)
You've typed "Sheild" a few times where you probably meant "Shield"
In the context you're using them, you should be using && instead of and, || instead of or, and ! instead of not. The reason is that or and and have such a low precedence that they will cause your assignment operators to not work the way you want. For example,
a = b and c
evaluates as
(a = b) and c
Such that a is always assigned the value b, and then in the result is truthy, c is evaluated (and discarded). On the other hand,
a = b && c
evaluates as
a = (b && c)
Which is what you want in this code.
Beyond that, I would move all of this code into a class, so that I can create lots of little methods for things:
class PositionChecker
def initialize(*args)
#x, #y, #z = *args
end
def checkPosition
...
end
end
Look for opportunities to replace local variables in checkPosition with method calls. For example, you could move borPolyBox into its own method (once all of the values it uses are methods of their own):
class PositionChecker
...
def borPolyBox
((modX <= borPolyBoxL)||(modY < borPolyBoxL)||(modZ <= borPolyBoxL)) && !((modX >= cdBoxL)||(modY >= cdBoxL)||(modZ >= cdBoxL)) && !(Math.sqrt(ySquared + zSquared) <= polyCylRad)
end
...
end
Once you've got all of these predicates as their own method, you can create a method to determine the material, like so:
def material
[
[:polyCylinder, 'poly'],
[:borPolyBox, 'borPoly'],
[:cadmiumShield, 'cd'],
[:leadShield, 'pb'],
].each do |method, name|
return name if send(method)
end
nil
end
And one for the position:
def position
[#x, #y, #z] if material
end
Continue along this line until nothing is left but a bag of teeny, focused methods.
Change all and and or to && and ||.
Never seen anyone actually use array.at(index) instead of array[index] before.
I also recommend against *args in favor of a Hash parameter as a kind of named parameters
def test(params)
x = params[:x] || raise("You have to provide x!")
y = params[:y] || raise("You have to provide y!")
z = params[:z] || raise("You have to provide z!")
puts x, y, z
end
and call it with (Ruby 1.9+ syntax)
test({x: 42, y: 4711, z: 93})
42
4711
93
Try using && instead of and, || instead of or and ! instead of not.
Your issue is probably a precedence one - read this article for more information.
Looking at your code, the way you are setting this up is to have the four "material" variables as booleans. Then you feed those bools into an if-elsif-else block.
The problem with this is that the very first if that returns true is going to exit the if-elsif-else block. If that's what you mean by keeping the first value it comes to, than that's an extremely predictable outcome.

Resources