Can't figure out how to iterate through keys in Ruby - ruby

I have a context for the session of what I'm doing. Like so:
class Context
CONTAINS = {}
end
I've been using it successfully like so:
Context::CONTAINS[:alpha] = "exampleA"
Context::CONTAINS[:beta] = "exampleB"
However :alphamight not contain anything some of the times the code runs. I was trying to iterate through it all by doing:
Context::CONTAINS.each { |x| puts x }
But that isn't working I get:
-:8:in `[]=': can't convert Symbol into Integer (TypeError)
I can't figure out how to iterate through it to just retrieve the :keys that actually have something and use them.

Use each_keys. It's the ruby-way of doing it, though you can also use each
Context::CONTAINS.each_key { |k|
puts k.to_s
}
to_s converts symbol to string.
Context::CONTAINS.keys is just another alias for each_key

Related

ruby code with the NoMethodError

When running the following Ruby code:
#!/usr/bin/env ruby
ar=[]
class String
def to_int
self == self.to_i
end
end
ARGV.each do |a|
ar.push("#{a}")
end
ar.map(&:to_int).sort
ar.each do |x|
print x + " "
end
puts ""
I am getting the following error:
example.rb:14:in `sort': undefined method `<=>' for false:FalseClass (NoMethodError)
This program needs to be running with the command line argument with a list of numbers. Any help will be appreciated.
ARGV.sort.each { |x| print x + " " }
puts
class String
def to_int
self == self.to_i
end
end
This to_int method will return either a true or false. So when you run this line: ar.map(&:to_int).sort, the map method will map the entire array into true or false.
You array will look like [false,false,true,false], which will fail when you run the sort function.
I'm not sure what the purpose of the to_int function is, you just need to map with the simple to_i function, then sort it.
ar.map!(&:to_i).sort
Make sure you use the map! so your original array is modified.
If you do map the array into integers, you will have to modify the print line to
ar.each do |x|
print x.to_s + " "
end
Otherwise you will get an error:
String can't be coerced into Fixnum
When I run this using Ruby 2.3.0, I don't get that error.
I tried Ruby 2.0.0p648 (which comes with OS X), 2.1.5, and 2.2.4, and they don't raise that error either.
It's a bit unclear to me what you're trying to accomplish here.
You're doing things that don't make any sense, but I'll assume you're trying to learn Ruby and you're just trying different things.
#!/usr/bin/env ruby
ar=[]
# This is "monkey patching" String, and is a bad practice.
class String
# A method named "to_int" implies a conversion to integer. But the "to_i" method already does
# that, and this method doesn't convert to an integer, it converts to a boolean.
def to_int
# Comparing the string to itself as an integer. Why would it ever be true?
self == self.to_i
end
end
# I'm assuming that this is intended to convert the argument list to a list of strings.
# But ARGV should already a list of strings.
# And this would be better done as `ar = ARGV.map(&:to_s)`
ARGV.each do |a|
ar.push("#{a}");
end
# This creates an array of values returned by `to_int`, then sorts the array.
# Since `String#to_int` (defined above) returns booleans, it's an array of "false" values, so
# sorting does nothing. But then the value of the `sort` is ignored, since it's not assigned to
# a variable. If you want to modify the order of an existing array, use `sort!`.
# But if you're trying to sort the array `ar` by the numeric values, `ar.sort_by(&:to_i)` would
# do that.
ar.map(&:to_int).sort
ar.each do |x| print x + " "; end
puts ""
It seems like you're trying to print the arguments in their numeric order.
This can be accomplished with
puts ARGV.sort_by(&:to_i).join(" ")

ruby 1.8 has find function, however after 1.9 there is not

In ruby1.8, String class has methods like find and find_all. The find usage is like following:
test.rb
a="123 123\n234 234\n456 456"
b = a.find { |ln| a.split[0] == ARGV[0] }
print b
I can get the following result:
# ./test.rb 123
123 123
However, after ruby 1.9, it fails because there is no find for the String instance. I need to handle these scripts. And I prefer to use the latest stable ruby.
I don't know if I can add a customized method for find and find_all, and how. I thought I can open String class, and add find method. However, I don't know how to receive the block as arguments, e.g., { |ln| a.split[0] == ARGV[0] }. Does anyone know any solutions?
You can use each_line which turns the list into an array of strings, and use Array's find to do the job
b = a.each_line.find { |ln| ln.split[0] == ARGV[0] }
# => "123 123\n"
If you want to patch String itself, you could create a delegation method in String.
class String
def find(*args, &block)
each_line.find(*args, &block)
end
end
now you should be able to keep your old code, and it should work.

Can ampersand be used to print a hash in Ruby?

Didn't really know how to title this question.
Consider an arbitrary hash (ENV is convenient):
ENV.each { |key, val| puts key + ': ' + val }
LC_MESSAGES: en_US.utf-8
LC_COLLATE: en_US.utf-8
PWD: /Users/baller/ive_fallen_and_i_cant_get_up
LC_MONETARY: en_US.utf-8
Is it possible to use the &: shorthand to do this?
foo.each(&:bar)
You can get away with two block variables and have one instead by doing this:
each{|kv| puts kv.join(": ")}
By default, no.
To do what you want, you would need to add a method to the Array class, which prints out the way you want, something like:
class Array
def bar
puts "#{self[0]}: #{self[1]}"
end
end
with that, ENV.each(&:bar) will do what you expect.
That said, I would not recommend this. Adding to a base class is something that should only be done when the utility far outweighs the potential for future conflicts, and the fact that this method is highly specialized for arrays with at least 2 elements in them.
Not related, but concatenating strings via + is measurably slower than using interpolation. It creates extra objects unnecessarily.
In general, this is possible if the elements of the collection respond to that method. For example:
class Foo
attr_reader :bar
def initialize(bar)
#bar = bar
end
end
foos = [Foo.new("one"), Foo.new("two"), Foo.new("three")]
p foos.map(&:bar) #=> ["one", "two", "three"]
This works because &symbol is syntactic sugar for symbol.to_proc, which in turn works some magic to return a block that sends that message to the object it receives as an argument.
While this won't work for your example (because the objects in ENV don't respond to :bar), you can pass a block that's been stored in a variable by using &:
block = lambda { |key, value| puts "#{key}: #{value}" }
ENV.each(&block)
In your example, using &:bar in place of a block will result in #to_proc being called on the given object, in this case, the symbol :bar. The #to_proc implementation of Symbol in Ruby basically expands foo.each(&:bar) to foo.each { |i| i.bar }. Since there are two arguments yielded from a hash, i in this example is an array of the key, value args. This is why you'd have to extend Array (as described by #x1a4) to get your hash to treat &:bar as expected.
As an alternative, you can create your own class that responds to #to_proc or simply implement your block as a Proc:
class Bar
def to_proc
Proc.new { |key, val| puts key + ': ' + val }
end
end
bar = Bar.new
# or
bar = Proc.new { |key, val| puts key + ': ' + val }
With a handle to bar, you can pass &bar in place of a block to hashes like ENV. So given a hash, foo:
foo.each(&bar)
A great post for more reading on this subject: http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

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?
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.
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">
]
}
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.
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.
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 } }

Resources