This bit of code:
var1 = 2
var2 = '5'
puts var1.to_s + var2
prints 25. I would like to know why. We convert 2 from an integer to a string 2, so in principle, it would be 2 + 5 = 7. But why is the output 25 instead?
'2' + '5' is '25' because String#+ concatenates two strings.
If you want to perform arithmetic, both operands need to be numbers, not strings.
In ruby, the code
x = '2' + '5'
is equivalent to:
x = '2'.+('5')
Try it--you can actually write that in your program. That second syntax may look really strange, but it fits the format:
x = someObject.someMethodName(someArgument)
where:
someObject => '2' (a String)
someMethodName => +
someArgument => '5' (a String)
Yep, + is actually the name of a method in the String class. So, you can check the String class for how the method named + works.
The syntax:
x = '2' + '5'
is known as syntactic sugar for the normal method syntax:
x = '2'.+('5')
And, because you cam omit the parentheses you could also write:
x = '2'.+ '5'
And if you don't like the result that the method named + in the String class produces, you can change it:
class String
def +(other)
return self.to_i + other.to_i
end
end
puts '2' + '5'
--output:--
7
Let's have some fun
2 + 5 = 7
2.class = Integer
5.class = Integer
'2' + '5' = '25'
'2'.class = String
'5'.class = String
so Integer + is numeric plus
and String + is concatenate equivalent
... but why ?
Where thing you need to understand is that Integer in Ruby is an object and String is an object too. All objects (in any OOP lang) has methods you can call on them:
2.public_methods
=> [:%, :&, :*, :+, :-, :/, :<, :>, :^, :|, :~, :-#, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :[], :inspect, :size, :succ, :to_int, :to_s, :to_i, :to_f, :next, :div, :upto, ....and the list goes on
'2'.public_methods
=> [:include?, :%, :*, :+, :to_c, :count, :unicode_normalize, :unicode_normalize!, :unicode_normalized?, :partition, :unpack, :unpack1, :sum, :next, ...and the list goes on
So in reality when you do:
2 + 5
'2' + '5'
you are actually calling a method .+
2.+(5)
'2'.+('5')
but in reallity calling a method in OOP languages is actually "sending" a message to an object:
2.send(:+, 5)
'2'.send(:+, '5')
Anyway,
one thing you need to realize is that if you are mixing different Objects together like:
'2' + 5
# => TypeError: no implicit conversion of Integer into String
2 + '5'
# => TypeError: String can't be coerced into Integer
Ruby will protect you by raising an error. Just in case you or your colegue are doing something stupid in your code
But nothing prevents you from transfering one object type to another object type
'2'.to_i + 5
#=> 7
2 + '5'.to_i
# => 7
'2' + 5.to_s
# => '25'
Another fun part of fully OOP languages (like Ruby) is you can create your own objects with + method
class SafePlus
def initialize(first_value)
#first_value = first_value
end
def +(other_value)
#first_value.to_i + other_value.to_i
end
end
SafePlus.new(2) + '5'
# => 7
SafePlus.new("5") + "107"
# => 112
You can also override the original implementation of String class or Integer class so all corresponding objects will behave this way:
module MyIntegerFix
def +(other)
super(other.to_i)
end
end
Integer.prepend(MyIntegerFix)
2 + "5"
# => 7
But for love of God don't do that !
And to fully confuse you, here is a mind blowing scenario is which you can actually overide single object
a = "5"
a + "2"
# => "52"
def a.+(other)
self.to_i + other.to_i
end
a + "2"
# => 7
#but
"5" + "2"
# => "52"
so in this case we are just overiding one single object, the a object not entire class of String.
you will not be able to override Integer objects this way because they are Singletons ...something that cannot be overwritten on single object base
Related
On a Rails project, I am gathering a hash with 10-15 key-value pairs, and passing it to a class (service object) for instantiation. The object properties should be set from the values in the hash except when there is no value (or nil). In this case, the property would desirably get set to a default value.
Instead of checking whether every value in the hash is not nil before creating an object, I would like to find a more efficient way of doing this.
I'm trying to use named parameters with default values. I don't know if this makes sense, but I would like to use the default value when the parameter is called with nil. I created a test for this functionality:
class Taco
def initialize(meat: "steak", cheese: true, salsa: "spicy")
#meat = meat
#cheese = cheese
#salsa = salsa
end
def assemble
"taco with: ##meat + ##cheese + ##salsa"
end
end
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chickenTaco = Taco.new(options1)
puts chickenTaco.assemble
# => taco with: chicken + false + mild
options2 = {}
defaultTaco = Taco.new(options2)
puts defaultTaco.assemble
# => taco with: steak + true + spicy
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3)
puts invalidTaco.assemble
# expected => taco with: pork + true + spicy
# actual => taco with: pork + +
If you want to follow a Object-Oriented approach, you could isolate your defaults in a separate method and then use Hash#merge:
class Taco
def initialize (args)
args = defaults.merge(args)
#meat = args[:meat]
#cheese = args[:cheese]
#salsa = args[:salsa]
end
def assemble
"taco with: #{#meat} + #{#cheese} + #{#salsa}"
end
def defaults
{meat: 'steak', cheese: true, salsa: 'spicy'}
end
end
Then following the suggestion by #sawa (thanks), use Rails' Hash#compact for your input hashes that have explicitly defined nil values and you will have the following output:
taco with: chicken + false + mild
taco with: steak + true + spicy
taco with: pork + true + spicy
EDIT:
If you do not want to use Rails' wonderful Hash#compact method, you can use Ruby's Array#compact method. Replacing the first line within the initialize method to:
args = defaults.merge(args.map{|k, v| [k,v] if v != nil }.compact.to_h)
Once you pass a value with a named parameter, access to the default value for that parameter is gone for that method call.
You either have to (i) assign the default value not in the method profile but in the method body as in sagarpandya82's answer, or (ii) remove the nil values before passing the arguments to the method like this using Rails' Hash#compact:
options3 = {:meat => "pork", :cheese => nil, :salsa => nil}
invalidTaco = Taco.new(options3.compact)
I don't think keyword arguments would be appropriate in your case. It seems a Hash is a better fit.
class Taco
attr_accessor :ingredients
def initialize(ingredients = {})
#ingredients = ingredients
end
def assemble
"taco with: #{ingredients[:meat]} + #{ingredients[:cheese]} + #{ingredients[:salsa]}"
end
end
You can even shorter the assemble method to list all the ingredients
def assemble
string = "taco with: " + ingredients.values.join(" + ")
end
And it will work as you'd expect
options1 = {:meat => "chicken", :cheese => false, :salsa => "mild"}
chicken_taco = Taco.new(options1)
puts chicken_taco.assemble() # output: taco with: chicken + false + mild
It is worth to mention that Ruby prefers chicken_tacos over chickenTacos.
I am doing a coding exercise(just to clear up any questions beforehand). My objective is to be able to offset the hash key by a specified amount. The problem I am having is if the hash key is a symbol. My approach is to turn it into a string and go from there. Here is my code:
class :: Hash
def transpose_key(offset)
self.each_key{|key|
t = (key.to_s.ord - "a".ord + offset)
key = (t % 26) + "a".ord.chr
}
return self
end #def
end #class
wrong_keys = { :a => "rope", :b => "knife", :x => "life-jacket", :z => "raft" }
puts wrong_keys.transpose_key(2)
I am getting the following error:
test.rb:6:in `+': String can't be coerced into Fixnum (TypeError)
I'm confused because I would think (key.to_s.ord) would give me a string letter on which to convert to ascii? I will later add functionality for numbered keys. Most of all I would like to, if at possible, use the approach I have started and make it work. Any ideas?
UPDATED
Here is my new code:
def transpose(string, offset)
#string = string.chars
string.each_codepoint {|c| puts (c + offset) > 122 ? (((c - 97) + offset) % 26 + 97).chr : (c + offset).chr}
end
transpose('xyz', 5)
...the output is correct, but it puts every character on different line. I have tried a various ways to try to join it, but can't seem to. If I use print in the iteration instead of puts, the output is joined, but I don't get a new line, which I want. Why is that and how can I fix it?
I'm confused because I would think (key.to_s.ord) would ...
That's the wrong line.
The next line is the line raising the error, and you're not doing .to_s.ord, you're doing .ord.to_s:
key = (t % 26) + "a".ord.chr
"a".ord.chr has no meaning, you're converting a character to an ordinal and back to a character, and then trying to add an integer and a character, hence your error. Replace "a".ord.chr with "a".ord
If I understand your question correctly, I think this gives you want you want:
class Hash
def transpose_key(offset)
map do |key, value|
t = (key.to_s.ord - "a".ord + offset) % 26
[(t + "a".ord).chr.to_sym, value]
end.to_h
end
end
wrong_keys = { :a => "rope", :b => "knife", :x => "life-jacket", :z => "raft" }
puts wrong_keys.transpose_key(2)
# {:c=>"rope", :d=>"knife", :z=>"life-jacket", :b=>"raft"}
Array#to_h (v2.0+) is an alternative to the class method Hash::[] (v1.0+)for converting an array of two-element arrays to a hash:
a = [[1,2],[3,4]]
Hash[a] #=> {1=>2, 3=>4}
a.to_h #=> {1=>2, 3=>4}
If we removed .to_h from the method we would find that the value returned by map (to which to_h is applied) is:
[[:c, "rope"], [:d, "knife"], [:z, "life-jacket"], [:b, "raft"]]
To use Hash#each_key, you could do this:
class Hash
def transpose_key(offset)
each_key.with_object({}) do |key,h|
t = (key.to_s.ord - "a".ord + offset) % 26
h[(t + "a".ord).chr.to_sym] = self[key]
end
end
end
puts wrong_keys.transpose_key(2)
# {:c=>"rope", :d=>"knife", :z=>"life-jacket", :b=>"raft"}
On reflection, I prefer the latter method.
I am just learning ruby and this seems to be an easy mistake I am doing here right?
def palindromic(str)
str.to_s
if str.reverse == str
puts "it is a palindromic number!"
end
end
palindromic(500)
Instead I am getting an error
Project4.rb:5:in `palindromic': undefined method `reverse' for 500:Fixnum (NoMet
hodError)
from Project4.rb:10:in `<main>'
You need to change the line str.to_s to str=str.to_s. One example to show you why so is below :
num = 12
num.to_s # => "12"
num # => 12
num=num.to_s
num # => "12"
Basically String#to_s change the receiver instance to the instance of String.But if the receiver is already the String instance,in that case receiver itself will be returned.
ar = [1,2]
ar.object_id # => 77603090
ar.to_s.object_id # => 77602480
str = 'Hello'
str.object_id # => 77601890
str.to_s.object_id # => 77601890
Is there a built-in method in Ruby to support this?
if you are in Rails, you can convert 1 to 1st, 2 to 2nd, and so on, using ordinalize.
Example:
1.ordinalize # => "1st"
2.ordinalize # => "2nd"
3.ordinalize # => "3rd"
...
9.ordinalize # => "9th"
...
1000.ordinalize # => "1000th"
And if you want commas in large numbers:
number_with_delimiter(1000, :delimiter => ',') + 1000.ordinal # => "1,000th"
in ruby you do not have this method but you can add your own in Integer class like this.
class Integer
def ordinalize
case self%10
when 1
return "#{self}st"
when 2
return "#{self}nd"
when 3
return "#{self}rd"
else
return "#{self}th"
end
end
end
22.ordinalize #=> "22nd"
How about Linguistics? Its not built in though. If you want built in , you have to set it up using hashes etc..
See here also for examples
I wanted an ordinalize method that has "first, second, third" rather than '1st, 2nd, 3rd' - so here's a little snippet that works up to 10 (and falls back to the Rails ordinalize if it can't find it).
class TextOrdinalize
def initialize(value)
#value = value
end
def text_ordinalize
ordinalize_mapping[#value] || #value.ordinalize
end
private
def ordinalize_mapping
[nil, "first", "second", "third", "fourth", "fifth", "sixth", "seventh",
"eighth", "ninth", "tenth" ]
end
end
Here's how it works:
TextOrdinalize.new(1).text_ordinalize #=> 'first'
TextOrdinalize.new(2).text_ordinalize #=> 'second'
TextOrdinalize.new(0).text_ordinalize #=> '0st'
TextOrdinalize.new(100).text_ordinalize #=> '100th'
if you are not in Rails you could do
def ordinalize(n)
return "#{n}th" if (11..13).include?(n % 100)
case n%10
when 1; "#{n}st"
when 2; "#{n}nd"
when 3; "#{n}rd"
else "#{n}th"
end
end
ordinalize 1
=> "1st"
ordinalize 2
=> "2nd"
ordinalize 11
=> "11th"
Using humanize gem, is probably the easiest way. But, yes, it is not built in, however it has only one dependency, so I think its a pretty good choice..
require 'humanize'
2.humanize => "two"
Why does ruby addition cannot coerce given string to fixnum and vice verca?
irb>fixnum = 1
=> 1
irb> fixnum.class
=> Fixnum
irb> string = "3"
=> "3"
irb> string.class
=> String
irb> string.to_i
=> 3
irb> fixnum + string
TypeError: String can't be coerced into Fixnum
from (irb):96:in `+'
from (irb):96
from :0
irb(main):097:0>
Because ruby doesn't know whether you want to convert the string to int or the int to string. In e.g. java 1 + "3" is "13". Ruby prefers to raise an error, so that the user can decide whether to to_s the one operand or to_i the other, rather than doing the wrong thing automatically.
It spits out an error because ruby doesn't know what you want. String + fixnum is an ambiguous statement. It could either mean that you want to add the int value of the string and the number, or the string value of the int and the string. For example:
str = "5"
num = 3
puts str.to_i + num
=> 8
puts str + num.to_s
=> 53
Rather than taking a guess as to which of those you want, ruby just throws an exception.
You need to set the variable back to itself, since doing a .to_i returns a integer value, but doesn't modify the original object.
>> string = "3"
=> "3"
>> string.class
=> String
>> string.to_i
=> 3
>> string
=> "3"
>> string.class
=> String
>> string = string.to_i
=> 3
>> string.class
=> Fixnum
Fixnum#+ is just a method. Simplified it works like this:
class Fixnum
def +(other)
if Fixnum === other
# normal operation
else
n1, n2= other.coerce(self)
return n1+n2
end
end
end
coerce is used for automatic numerical type conversion. This is used for example if you do 42 + 3.141. A string in ruby cannot automaticly be converted into numerical values. You could enhance the String class to be able to do this. You just need to do
class String
def coerce(other)
coerced= case other
when Integer
self.to_i
when
self.to_f
end
return coerced, other
end
end
Now you can do
23 + "42" # => 65
0.859 - "4" # => 3.141
This does not work the other way arround. coerce is only for numerical values "23" + 42 won't work. String#+ will not use coerce.
The simplified + is done in Fixnum and not in Integer on purpose. Fixnum and Bignum have their separate methods, because they work internally very different.