Type-indifferent comparison in Ruby - ruby

I'm sure this is a basic question in Ruby:
Is there a way to check if
a == b
even if a is an integer and b is a string? I realize that I can do
a.to_s == b.to_s
but I'd like to know if there's some other, better way.

I'm not sure that I understand the question. If I do understand it, I think you're trying to slip one by the interpreter:
telemachus ~ $ irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> b = '1'
=> "1"
irb(main):003:0> a == b
=> false
You can compare 1 and '1' all you like, but they aren't equal according to the way Ruby handles strings and numbers. In a nutshell, Ruby ain't Perl. (Edit: I should clarify. Clearly the number 1 is not the same thing as the string '1'. So it's not really a question of how Ruby handles them. If you compare them directly, they're just not the same. I just meant that Ruby doesn't do automatic conversions the way that Perl would. Depending on what language you come from and your attitude towards typing, this will make you happy or suprised or annoyed or some combination of these.)

What about something like:
class Object
def compare_as_strings(other)
return self.to_s == other.to_s
end
end
I hate extending something that fundamental, but this DOES work...
>> a = 1
=> 1
>> b = "1"
=> "1"
>> a.compare_as_strings(b)
=> true

I haven't run into one, and Rails gets tripped up by this regularly, so I suspect that there's no slick way to do it - you have to force the to_s.

Related

Why does random work like this in Ruby?

I was trying to be clever about deterministically picking random stuff, and found this:
irb(main):011:0> Random.new(Random.new(1).rand + 1).rand == Random.new(1).rand
=> true
irb(main):012:0> Random.new(Random.new(5).rand + 1).rand == Random.new(5).rand
=> false
irb(main):013:0> Random.new(Random.new(5).rand + 5).rand == Random.new(5).rand
=> true
For a second, I thought "wow, maybe that's a property of random number generators", but Python and C# fail to reproduce this.
You’re probably going to be disappointed with this one. Let’s take a look at the output of rand:
irb(main):001:0> Random.rand
0.5739704645347423
It’s a number in the range [0, 1). Random.new accepts an integer seed.
irb(main):002:0> Random.new(5.5) == Random.new(5)
true
Mystery solved!

Ruby if statement multiline condition syntax?

Hi all new to ruby is this if statement valid syntax?
if (verify_login_id = Login.where(params[:email_address], "active" =>1).select('id')# => [#<Login id: 767>]
verify_admin_id = Admin.where("login_id" => verify_login_id.first.id).exists? #=> true)
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Although setting variable values within an if statement is syntactically valid, it is very problematic.
It can be mistakenly read as a comparison rather than assignment, and in many cases it is a result of someone trying to make a comparison and confusing the equals predicate == with the assignment operator =. That's why a lot of IDEs today mark such code as a warning and a probable error.
In your code it also seems quite unneeded... Break it into two more readable lines:
verified_login = Login.where(params[:email_address], "active" =>1).select('id').first # => [#<Login id: 767>]
if verified_login && Admin.where("login_id" => verified_login.id).exists? #=> true
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Some more observations - your assignment to verify_login_id is not an id, but a Login object, and your assignment to verify_admin_id is not an id either - but a boolean (and you are not using it anyway). This might seem besides the point - but it adds up to an unreadable and an unmaintainable code.
No it is not a valid code. In the second line, it contains an invalid #-comment that has removed a single ) from the code. (exact snip is #=>true) and should be ) #=>true)
I've removed the comments from the code, added the missing parenthesis and the parser seems to accept it. I couldn't run it of course. So, try this one, it might work:
if (verify_login_id = Login.where(params[:email_address], "active" =>1).select('id')
verify_admin_id = Admin.where("login_id" => verify_login_id.first.id).exists?)
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Regarding multiline condition in the if - yes, it is possible. Sometimes directly, sometimes with a small trick. Try this:
if (1+2 >=
3)
puts "as"
end
It works (at least on Ruby 1.9.3). However, this will not:
if (1+2
>=
3)
puts "as"
end
This is because of some internal specific of how Ruby interpreter/compiler is designed. In the last example, to make it work, you need to inform Ruby that the line has not ended. The following example works:
if (1+2 \
>=
3)
puts "as"
end
Note the \ added at the end of problematic line

Lazy-evaluation of a "#{}"-string in ruby

I started to put print-statements throughout my code. So as not to clutter up the output, I did something like:
dputs LEVEL, "string"
where LEVEL is 0 for errors, 1 for important .. 5 for verbose and is compared to DEBUG_LEVEL. Now my problem is, that in a statement like:
dputs 5, "#{big_class.inspect}"
the string is always evaluated, also if I set DEBUG_LEVEL to 1. And this evaluation can take a long time. My favourite solution would be something like:
dputs 5, '#{big_class.inspect}'
and then evaluate the string if desired. But I don't manage to get the string in a form I can evaluate. So the only think I could come up with is:
dputs( 5 ){ "#{big_class.inspect}" }
but this looks just ugly. So how can I evaluate a '#{}' string?
You could do this by having dputs use sprintf (via %). That way it can decide not to build the interpolated string unless it knows it's going to print it:
def dputs(level, format_str, *vars)
puts(format_str % vars) if level <= LEVEL
end
LEVEL = 5
name = 'Andrew'
dputs 5, 'hello %s', name
#=> hello Andrew
Or, as you suggest, you can pass a block which would defer the interpolation till the block actually runs:
def dputs(level, &string)
raise ArgumentError.new('block required') unless block_given?
puts string.call if level <= LEVEL
end
I think it's of no value whatsoever, but I just came up with:
2.3.1 :001 > s = '#{a}'
=> "\#{a}"
2.3.1 :002 > a = 1
=> 1
2.3.1 :003 > instance_eval s.inspect.gsub('\\', '')
=> "1"
2.3.1 :004 > s = 'Hello #{a} and #{a+1}!'
=> "Hello \#{a} and \#{a+1}!"
2.3.1 :005 > instance_eval s.inspect.gsub('\\', '')
=> "Hello 1 and 2!"
Don't use that in production :)
I don't think you can dodge the ugly there. The interpolation happens before the call to dputs unless you put it inside a block, which postpones it until dputs evaluates it. I don't know where dputs comes from, so I'm not sure what its semantics are, but my guess is the block would get you the lazy evaluation you want. Not pretty, but it does the job.
OK, obviously I was just too lazy. I thought there must be a more clean way to do this, Ruby being the best programming language and all ;) To evaluate a string like
a = '#{1+1} some text #{big_class.inspect}'
only when needed, I didn't find a better way than going through the string and eval all "#{}" encountered:
str = ""
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ){
str += $1
str += eval( $2[2..-2] ).to_s
}
if you're not into clarity, you can get rid of the temporary-variable str:
"#{b}\#{}".scan( /(.*?)(#\{[^\}]*\})/ ).collect{|c|
c[0] + eval( c[1][2..-2] ).to_s
}.join
The String.scan-method goes through every '#{}'-block, as there might be more than one, evaluating it (the 2..-2 cuts out the "#{" and "}") and putting it together with the rest of the string.
For the corner-case of the string not ending with a '#{}'-block, an empty block is added, just to be sure.
But well, after being some years in Ruby, this still feels clunky and C-ish. Perhaps it's time to learn a new language!

Ruby min max assignment operators

When programming ruby I always find myself doing this:
a = [a, b].min
This means compare a and b and store the smallest value in a. I don't like writing the code above as I have to write a twice.
I know that some non-standard dialects of C++ had an operator which did exactly this
a <?= b
Which I find very convenient. But I'm not really interested in the operator as much as I'm in the feature of avoiding repetition. I would also be happy if I could write
a.keep_max(b)
a can be a quite long variable, like my_array[indice1][indice2], and you don't want to write that twice.
I did alot of googling on this and found no result, hopefully this question will pop up and be useful for others aswell.
So, is there any non-repeitive way to express what I want in ruby?
What you would like to do is in fact not possible in ruby (see this question). I think the best you can do is
def max(*args)
args.max
end
a = max a, b
I don't understand your question. You can always do something like this ...
module Comparable
def keep_min(other)
(self <=> other) <= 0 ? self : other
end
def keep_max(other)
(self <=> other) >= 0 ? self : other
end
end
1.keep_min(2)
=> 1
1.keep_max(2)
=> 2
Well, that won't work for all objects with <=> because not all of them are implementing Comparable, so you could monkey-patch Object.
Personally I prefer clarity and tend to avoid monkey-patching. Plus, this clearly is a binary predicate, just like "+", therefore method-chaining doesn't necessarily make sense so I prefer something like this to get rid of that array syntax:
def min(*args)
args.min
end
def max(*args)
args.max
end
min(1, 2)
=> 1
max(1, 2)
=> 2
But hey, I'm also a Python developer :-)
You can define your own method for it:
class Object
def keep_max(other)
[self, other].max
end
end
a = 3
b = 7
puts a.keep_max(b)
But you should be careful defining methods on Object as it can have unpredictable behaviour (for example, if objects cannot be compared).
def keep_max(var, other, binding)
eval "#{var} = [#{var}, #{other}].max", binding
end
a = 5
b = 78
keep_max(:a, :b, binding)
puts a
#=> 78
This basically does what you want. Take a look at Change variable passed in a method

Ruby case statement with multiple variables using an Array

I'd like to compare multiple variables for a case statement, and am currently thinking overriding the case equals operator (===) for Array is the best way to do it. Is this the best way?
Here is an example use case:
def deposit_apr deposit,apr
# deposit: can be nil or 2 length Array of [nil or Float, String]
# apr: can be nil or Float
case [deposit,apr]
when [[Float,String],Float]
puts "#{deposit[0]} #{deposit[1]}, #{apr*100.0}% APR"
when [[nil,String],Float]
puts "#{apr*100.0}% APR on deposits greater than 100 #{deposit[1]}"
when [[Float,String],nil]
puts "#{deposit[0]} #{deposit[1]}"
else
puts 'N/A'
end
end
The only problem is the Array case equals operator doesn't apply the case equal to the elements of the Array.
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
N/A
It will if I override, but am not sure what I'd be breaking if I did:
class Array
def ===(other)
result = true
self.zip(other) {|bp,ap| result &&= bp === ap}
result
end
end
Now, it all works:
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
656.0 rupees, 6.5% APR
Am I missing something?
I found this question because I was looking to run a case statement on multiple variables, but, going through the following, came to the conclusion that needing to compare multiple variables might suggest that a different approach is needed. (I went back to my own code with this conclusion, and found that even a Hash is helping me write code that is easier to understand.)
Gems today use "no monkey patching" as a selling point. Overriding an operator is probably not the right approach. Monkey patching is great for experimentation, but it's too easy for things to go awry.
Also, there's a lot of type-checking. In a language that is designed for Duck Typing, this clearly indicates the need for a different approach. For example, what happens if I pass in integer values instead of floats? We'd get an 'N/A', even though that's not likely what we're looking for.
You'll notice that the example given in the question is difficult to read. We should be able to find a way to represent this logic more clearly to the reader (and to the writer, when they revisit the code again in a few months and have to puzzle out what's going on).
And finally, since there are multiple numbers with associated logic, it seems like there's at least one value object-type class (Deposit) that wants to be written.
For cleanliness, I'm going to assume that a nil APR can be considered a 0.0% APR.
class Deposit
def initialize(amount, unit='USD', options={})
#amount = amount.to_f # `nil` => 0.0
#unit = unit.to_s # Example assumes unit is always present
#apr = options.fetch(:apr, 0.0).to_f # `apr: nil` => 0.0
end
end
Once we have our Deposit object, we can implement the print logic without needing case statements at all.
class Deposit
# ... lines omitted
def to_s
string = "#{#amount} #{#unit}"
string << ", #{#apr * 100.0}% APR" if #apr > 0.0
string
end
end
d = Deposit.new(656.00, 'rupees', apr: 0.065)
d.to_s
# => "656.0 rupees, 6.5% APR"
e = Deposit.new(100, 'USD', apr: nil)
e.to_s
# => "100.0 USD"
f = Deposit.new(100, 'USD')
f.to_s
# => "100.0 USD"
Conclusion: If you're comparing multiple variables in a case statement, use that as a smell to suggest a deeper design issue. Multiple-variable cases might indicate that there's an object that wants to be created.
If you are worried about breaking something by changing Array behavior, and certainly that's a reasonable worry, then just put your revised operator in a subclass of Array.
it's definitely not the best way. even more - you should not redefine methods of standart classes as core functionality may depend on it - have fun debugging then.
defensive style is nice(with lot of type checks and whatnot) but it usually hurts performance and readability.
if you know that you will not pass anything else than bunch of floats and strings to that method - why do you need all those checks for?
IMO use exception catching and fix the source of problem, don't try to fix the problem somewhere in the middle

Resources