ceth
ceth

Reputation: 45295

TypeError: can't convert String into Integer

I have code:

class Scene
  def initialize(number)
    @number = number
  end
  attr_reader :number
end

scenes = [Scene.new("one"), Scene.new("one"), Scene.new("two"), Scene.new("one")]

groups = scenes.inject({}) do |new_hash, scene|
   new_hash[scene.number] = [] if new_hash[scene.number].nil?
   new_hash[scene.number] << scene
end

When I'm lauching it I get error:

freq.rb:11:in `[]': can't convert String into Integer (TypeError)
       from freq.rb:11:in `block in <main>'
       from freq.rb:10:in `each'
       from freq.rb:10:in `inject'
       from freq.rb:10:in `<main>'

If I change scenes to:

scenes = [Scene.new(1), Scene.new(1), Scene.new(2), Scene.new(1)]

the problem dissapear.

Why I get error message in the first case? Why Ruby decide to convert scene.number from String to Integer?

And one additional question about the 'inject' method. When Ruby initialize the 'new_hash' variable and how can Ruby know the type of this variable?

Upvotes: 5

Views: 24593

Answers (5)

Harish Shetty
Harish Shetty

Reputation: 64363

I know an answer is accepted for this question, but I can't help but post my answer.

groups = scenes.inject({}) { |nh, s| nh.tap {|h| (h[s.number] ||= []) << s } }

Upvotes: 0

hurikhan77
hurikhan77

Reputation: 5931

Why not use group_by which is probably exactly what you try to accomblish?

groups = scenes.group_by(&:number)
# => {"two"=>[#<Scene:0xb728ade0 @number="two">],
#     "one"=>
#       [#<Scene:0xb728ae30 @number="one">,
#        #<Scene:0xb728ae08 @number="one">,
#        #<Scene:0xb728ada4 @number="one">]}

inject is a folding operation and not exactly what you want. At least it's cumbersome to use in this way. merge with a block would probably be appropriate if you want to apply some algorithm during merging or grouping.

Upvotes: 2

benm
benm

Reputation: 96

Z.E.D.'s right. See Jay Fields' Thoughts: Ruby: inject for a good explanation of inject by example.

As presented, your block returns an array. So the new_hash in |new_hash, scene| ends up being that array. When Ruby tries to find the array index 'one', it throws the error because 'one' is a String, not an Integer.

All you need to do is return new_hash as Z.E.D. showed, and you'll get something like this:

{
  "two" => [
    #<Scene:0x101836470 @number="two">
  ],
  "one" => [
    #<Scene:0x101836510 @number="one">,
    #<Scene:0x1018364c0 @number="one">,
    #<Scene:0x101836420 @number="one">
  ]
}

Upvotes: 6

Dave Everitt
Dave Everitt

Reputation: 17876

Also, to explain 'how can Ruby know the type of this variable' and why it tries to 'convert String into Integer' you might want to revise: Ruby variables and dynamic typing.

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160551

try:

groups = scenes.inject({}) do |new_hash, scene|
   new_hash[scene.number] = [] if new_hash[scene.number].nil?
   new_hash[scene.number] << scene
   new_hash
end

Ruby takes the empty hash passed into inject() and sets new_hash to that. When the block ends the return value gets used to initialize new_hash the next time through, i.e., new_hash keeps accumulating the result of the block.

In your original code you were not returning the hash but an array (new_hash[scene.number] is an array) and the next loop through Ruby complained because new_hash[scene.number] was trying to do a lookup into the array with a string value, hence the error you got.

Upvotes: 10

Related Questions