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.
Related
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
I get the text containing the sum from an element:
sum = browser.div(:class => 'single sort', :index => 0).div(:class, 'amount').text
Then I have to compare sum with another integer, but sum is still a string:
>> sum = browser.div(:class => 'single sort', :index => 0).div(:class, 'amount').text
=> "7 671 \u0420"
>> puts sum.class
String
>> sum.gsub!(/[^0-9]/, '')
=> "7671"
>> puts sum.class
String
>> sum.to_i
=> 7671
>> puts sum.class
String
How can I convert sum to an integer?
You still have to assign the new value to sum:
sum = sum.to_i
You have to override the results in this way:
sum = sum.to_i
I suggest to use Integer(sum) instead of .to_i method to raise an exception if you try to convert a not number string.
nil.to_i = 0
"".to_i = 0
Integer("")
ArgumentError: invalid value for Integer(): ""
from (irb):1:in `Integer'
from (irb):1
from /Users/nico/.rvm/rubies/ruby-2.2.0/bin/irb:11:in `<main>'
String#to_i returns a new Object (which is the only sensible implementation; think a moment about what would happen if String#to_i magically changed the object's class).
So, to assign the integer value to your sum variable, use
sum = sum.to_i
or (better) introduce a new variable for your integer value:
sum_as_integer = sum.to_i
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 get TypeError: no implicit conversion of String into Integer Couldn't figure out what is wrong here.
require 'json'
h = '{"name":[{"first":"first ", "last":"last"}], "age":2}'
h = JSON.parse(h)
class C
def fullname(p)
first(p["name"]) + last(p["name"])
end
def age(p)
p["age"]
end
private
def first(name)
name["first"]
end
def last(name)
name["last"]
end
end
C.new.age(h) #=> 2
C.new.fullname(h) #=> TypeError: no implicit conversion of String into Integer
Name is an array, you have two options:
Option A:
Give fullname an element of the array:
def fullname(elem)
first(elem) + last(elem)
end
And call it with
C.fullname(p.first)
for instance
Option B:
Assume that it's always the first element of the array in fullname
def fullname(p)
name=p["name"].first
first(name) + last(name)
end
Don't be confused by Array.first which is Array[0] and your 'first' function
The result of h["name"] is name = [{"first" => "first ", "last" => "last"}], which is an array. You cannot apply name["first"] or name["last"]. The argument passed to an array has to be an integer.
"name" is an Array. fullname(p) should read
first(p["name"][0]) + last(p["name"][0])
How i can convert string into hash?
now i use:
eval "{'1627207:28320'=>'text'}"
=> {'1627207:28320'=>'text'}
but "eval" is not good for my case - string passed from params, and such case it is not secure
Edited:
passed string can also be:
"{'1627207'=>'text', '11:167:28320'=>'text 1 / text 2 / unicode=>привет!'}"
Then need result hash:
{'1627207:28320'=>'text',
'11:167:28320'=>'text 1 / text 2 / unicode=>привет!'}
str = "{'1627207:28320'=>'text'}"
p Hash[*str.delete("{}'").split('=>')] #{"1627207:28320"=>"text"}
edit for different input:
str = "{'1627207:28320'=>'text', 'key2'=>'text2'}"
p Hash[*str.delete("{}'").split(/=>|, /)] #{"1627207:28320"=>"text", "key2"=>"text2"}
class String
def to_h
h={}
self.scan(/'(\w+.\w+)'=>'(\w+)'/).each { |k,v| h[k]=v }
h
end
end
p "{'1627207:28320'=>'text','test'=>'text2'}".to_h
=>{"1627207:28320"=>"text", "test"=>"text2"}
EDIT: shorter version
class String
def to_h
Hash[self.scan(/'([^']+)'=>'([^']+)'/)]
end
end
Quite straight forward:
$ irb
irb(main):001:0> k='1627207:28320'
=> "1627207:28320"
irb(main):002:0> v='text'
=> "text"
irb(main):003:0> h={k => v}
=> {"1627207:28320"=>"text"}
irb(main):004:0> h
=> {"1627207:28320"=>"text"}
irb(main):005:0>
You could simply try this:
text_hash['1627207:28320'] = 'text'
text_hash