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])
Related
How does one write Ruby methods for modification in place?
I want to accomplish the following:
def fulljoin(ruby_array)
r = ''
ruby_array.each {|item| r += "'#{ item }', "}
r.chop!.chop!
end
a = ['Alex', 'Bert', 'Charlie']
a = fulljoin(a) # => 'Alex', 'Bert', 'Charlie'
But I want to modify the array a in place:
a.fulljoin!
What is the syntax to accomplish this?
Initially a is an Array. If you could write method a.fulljoin! with desirable result, a would become a String, but it's not possible in Ruby.
But a.fulljoin! can convert a to Array with single member a[0] - a
String you need. And it will be as close to your goal as possible:
class Array
def fulljoin!
r = "'#{self.join("', '")}'"
self.clear
self[0] = r
end
end
a = ["Alex", "Bert", "Charlie"]
a.fulljoin!
p a
=> ["'Alex', 'Bert', 'Charlie'"]
P.S.: As suggested by #engineersmnky, method fulljoin! can be simplified to:
class Array
def fulljoin!
self.replace(["'#{self.join("', '")}'"])
end
end
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'm trying to solve this exercise from Ruby Monk website, which says:
Try implementing a method called occurrences that accepts a string
argument and uses inject to build a Hash. The keys of this hash should
be unique words from that string. The value of those keys should be
the number of times this word appears in that string.
I've tried to do it like this:
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
But I always get this error:
TypeError: no implicit conversion of String into Integer
Meanwhile, the solution for this one is quite the same (I think):
def occurrences(str)
str.scan(/\w+/).inject(Hash.new(0)) do |build, word|
build[word.downcase] +=1
build
end
end
Okay so your issue is that you are not returning the correct object from the block. (In your case a Hash)
#inject works like this
[a,b]
^ -> evaluate block
| |
-------return-------- V
In your solution this is what is happening
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1
#=> 1
#second pass uses the result from the first as `a` so `a` is now an integer (1).
#So instead of calling Hash#[] it is actually calling FixNum#[]
#which requires an integer as this is a BitReference in FixNum.Thus the `TypeError`
Simple fix
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1; a }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1; a
#=> {"word" => 1}
Now the block returns the Hash to be passed to a again. As you can see the solution returns the object build at the end of the block thus the solution works.
Edit: The issue is being unable to get the quantity of arrays within the hash, so it can be, x = amount of arrays. so it can be used as function.each_index{|x| code }
Trying to use the index of the amount of rows as a way of repeating an action X amount of times depending on how much data is pulled from a CSV file.
Terminal issued
=> Can't convert symbol to integer (TypeError)
Complete error:
=> ~/home/tests/Product.rb:30:in '[]' can't convert symbol into integer (TypeError) from ~home/tests/Product.rub:30:in 'getNumbRel'
from test.rb:36:in '<main>'
the function is that is performing the action is:
def getNumRel
if defined? #releaseHashTable
return #releaseHashTable[:releasename].length
else
#releaseHashTable = readReleaseCSV()
return #releaseHashTable[:releasename].length
end
end
The csv data pull is just a hash of arrays, nothing snazzy.
def readReleaseCSV()
$log.info("Method "+"#{self.class.name}"+"."+"#{__method__}"+" has started")
$log.debug("reading product csv file")
# Create a Hash where the default is an empty Array
result = Array.new
csvPath = "#{File.dirname(__FILE__)}"+"/../../data/addingProdRelProjIterTestSuite/releaseCSVdata.csv"
CSV.foreach(csvPath, :headers => true, :header_converters => :symbol) do |row|
row.each do |column, value|
if "#{column}" == "prodid"
proHash = Hash.new { |h, k| h[k] = [ ] }
proHash['relid'] << row[:relid]
proHash['releasename'] << row[:releasename]
proHash['inheritcomponents'] << row[:inheritcomponents]
productId = Integer(value)
if result[productId] == nil
result[productId] = Array.new
end
result[productId][result[productId].length] = proHash
end
end
end
$log.info("Method "+"#{self.class.name}"+"."+"#{__method__}"+" has finished")
#productReleaseArr = result
end
Sorry, couldn't resist, cleaned up your method.
# empty brackets unnecessary, no uppercase in method names
def read_release_csv
# you don't need + here
$log.info("Method #{self.class.name}.#{__method__} has started")
$log.debug("reading product csv file")
# you're returning this array. It is not a hash. [] is preferred over Array.new
result = []
csvPath = "#{File.dirname(__FILE__)}/../../data/addingProdRelProjIterTestSuite/releaseCSVdata.csv"
CSV.foreach(csvPath, :headers => true, :header_converters => :symbol) do |row|
row.each do |column, value|
# to_s is preferred
if column.to_s == "prodid"
proHash = Hash.new { |h, k| h[k] = [ ] }
proHash['relid'] << row[:relid]
proHash['releasename'] << row[:releasename]
proHash['inheritcomponents'] << row[:inheritcomponents]
# to_i is preferred
productId = value.to_i
# this notation is preferred
result[productId] ||= []
# this is identical to what you did and more readable
result[productId] << proHash
end
end
end
$log.info("Method #{self.class.name}.#{__method__} has finished")
#productReleaseArr = result
end
You haven't given much to go on, but it appears that #releaseHashTable contains an Array, not a Hash.
Update: Based on the implementation you posted, you can see that productId is an integer and that the return value of readReleaseCSV() is an array.
In order to get the releasename you want, you have to do this:
#releaseHashTable[productId][n][:releasename]
where productId and n are integers. Either you'll have to specify them specifically, or (if you don't know n) you'll have to introduce a loop to collect all the releasenames for all the products of a particular productId.
This is what Mark Thomas meant:
> a = [1,2,3] # => [1, 2, 3]
> a[:sym]
TypeError: can't convert Symbol into Integer
# here starts the backstrace
from (irb):2:in `[]'
from (irb):2
An Array is only accessible by an index like so a[1] this fetches the second element from the array
Your return a an array and thats why your code fails:
#....
result = Array.new
#....
#productReleaseArr = result
# and then later on you call
#releaseHashTable = readReleaseCSV()
#releaseHashTable[:releasename] # which gives you TypeError: can't convert Symbol into Integer
I have a loop which should assign different variable names depending on filesname which are contained in an array.
Each variable is set as an empty array.
filenames = ['file1', 'file2']
filenames.each do |filename|
"#data_" + "#{filename}" = [] # used # as I need these variables externaly from the loop
end
This gives me the following error
syntax error, unexpected '=', expecting keyword_end
What I don't understand is if I use
filenames.each do |filename|
p "#data_" + "#{filename}"
end
it renders correctly
#data_file1
#data_file2
And if I set an empty array through the loop
filenames.each do |filename|
#data = []
end
p #data
#=> []
it works too...
Thanks for your help!
I would recommend using a data structure rather than synthesizing instance variables1 with meta-programming.
Why not just:
#data = filenames.inject({}) { |h, v| h[v] = []; h }
# => { "file1" => [], "file2" => [] }
Now you can just reference #data['file1'] where you want the 'file1' Array object.
1. But for the record, here is one way:
class A
def f x, y
self.class.send :attr_accessor, x
send "#{x}=", y
end
end
a = A.new
a.f 'new_instance_variable', 12345
p a.new_instance_variable
Simplest solution, if you're sure about the sanitsed-ness of your filenames array, would be:
filenames = ['file1', 'file2']
filenames.each do |filename|
eval "#data_#{filename} = []"
end
What you're doing in "#data_" + "#{filename}" = [] is assigning empty Array instance ([]) to a String instance ("#data_file1"), hence syntax error, unexpected '=', expecting keyword_end
You should want to do is to assign [] to instance variable: #data_file, not string: "#data_file".
You can achieve that with instance_variable_set method of class Object:
filenames = ['file1', 'file2']
filenames.each do |filename|
instance_variable_set("#data_#{filename}".to_sym, [])
end
Why are you using string concatenation and interpolation in the same string...
something like
p "#data_#{filename}"
is the same as your
p "#data_" + "#{filename}"
Now this won't work because you are trying to say string = something else you need to evaluate the string
eval("#data_#{filename} = []")