Ruby function as value of hash - ruby

I was wondering if or how it is possible to map a function to a value of a hash.
For example:
----Start Class------------
def foo(var)
return var + 2
end
hash_var = { func => foo() }
----End Class--------------
so that I could later call
Class::hash_var["func"][10]
or
Class::hash_var["func"](10)
and that would return 12?

You could use method method.
def foo(var)
return var + 2
end
hash_var = { :func => method(:foo) }
hash_var[:func].call(10)

Functions/methods are one of the few things in Ruby that are not objects, so you can't use them as keys or values in hashes. The closest thing to a function that is an object would be a proc. So you are best off using these...
The other answers pretty much listed all possible ways of how to put a proc into a hash as value, but I'll summarize it nonetheless ;)
hash = {}
hash['variant1'] = Proc.new {|var| var + 2}
hash['variant2'] = proc {|var| var + 2}
hash['variant3'] = lambda {|var| var + 2}
def func(var)
var + 2
end
hash['variant4'] = method(:func) # the *method* method returns a proc
# describing the method's body
there are also different ways to evaluate procs:
hash['variant1'].call(2) # => 4
hash['variant1'][2] # => 4
hash['variant1'].(2) # => 4

You can set the value to a Proc and call it.
hash_var = {
'func' => proc {|x| x+2}
}
hash_var['func'].call(10) #=> 12

Try using lambdas
hash_var = { :func => lambda { |var| var + 2 }}
hash_var['func'].call(5) #=> 7

Another option would be:
def foo(name)
puts "Hi #{name}"
end
def bar(numb)
puts numb + 1
end
hash_var = { func: :foo, func2: :bar }
send hash_var[:func], "Grid"
# => Hi Grid
send hash_bar[:func2], 3
# => 4
Here is some information about the #send method What does send() do in Ruby?

Related

How can i dynamically call a Proc in Ruby?

Is there a method.send equivalent for proc?
eg:
def s a
a + 1
end
b = "s"
send b.to_sym,10 #=> 11
Is there something like this?
p = Proc.new{|s| s + 1}
d = "p"
*call d.to_sym,10 *
EDIT:
In response to mudasobwa's answer
I need to call the Procs from an array of methods.
eg:
ss = ["a","b","c","d"]
Is it possible in this case?
The other answers cover the exact question asked. But I say, it was a wrong approach. Don't do that runtime introspection. It brings no benefit. If you want to address your procs by name, put them in a hash and use "civilian-grade" ruby.
handlers = {
'process_payment' => proc { ... },
'do_this' => proc { ... },
'or_maybe_that' => proc { ... },
}
pipeline = ['process_payment', 'or_maybe_that']
pipeline.each do |method_name|
handlers[method_name].call
end
For this particular example:
p = Proc.new{|s| s + 1}
d = "p"
#⇒ *call d.to_sym,10 *
It would be:
binding.local_variable_get(d).(10)
#⇒ 11
Updated
Procs are objects, so you can store them in variables, arrays, hashs, just like any objects, and call them from those rather than by names.
If you need to make an array of procs, store the procs themself in an array, rather than the names of the variables you assigned them to. This way, you can pass this array around and call them all.
myprocs = []
myprocs << = Proc.new{|s| s + 1}
myprocs.each {|p| p.call(10)}
If you want to call them by names, use a hash.
myprocs = {}
myprocs["d"] = Proc.new{|s| s + 1}
myprocs["d"].call(10)
Using eval - bad practice, but as one of the possible solutions is:
p = Proc.new{|s| s + 1}
d = "p"
eval("#{d}[10]")
#=> 11

Passing a method that takes arguments as an argument in Ruby

I'd like to calculate the difference for various values inside 2 hashes with the same structure, as concisely as possible. Here's a simplified example of the data I'd like to compare:
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
I have a very simple method to get the value I want to compare. In reality, the hash can be nested a lot deeper than these examples, so this is mostly to keep the code readable:
def get_y(data)
data["x"]["y"]
end
I want to create a method that will calculate the difference between the 2 values, and can take a method like get_y as an argument, allowing me to re-use the code for any value in the hash. I'd like to be able to call something like this, and I'm not sure how to write the method get_delta:
get_delta(hash1, hash2, get_y) # => 8
The "Ruby way" would be to pass a block:
def get_delta_by(obj1, obj2)
yield(obj1) - yield(obj2)
end
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
get_delta_by(hash1, hash2) { |h| h["x"]["y"] }
#=> 8
A method could be passed (indirectly) via:
def get_y(data)
data["x"]["y"]
end
get_delta_by(hash1, hash2, &method(:get_y))
#=> 8
Building on Stefan's response, if you want a more flexible get method you can actually return a lambda from the function and pass arguments for what you want to get. This will let you do error handling nicely:
Starting with the basics from above...
def get_delta_by(obj1, obj2)
yield(obj1) - yield(obj2)
end
hash1 = {"x" => { "y" => 20 } }
hash2 = {"x" => { "y" => 12 } }
get_delta_by(hash1, hash2) { |h| h["x"]["y"] }
Then we can define a get_something function which takes a list of arguments for the path of the element to get:
def get_something(*args)
lambda do |data|
args.each do |arg|
begin
data = data.fetch(arg)
rescue KeyError
raise RuntimeError, "KeyError for #{arg} on path #{args.join(',')}"
end
end
return data
end
end
Finally we call the function using the ampersand to pass the lambda as a block:
lambda_getter = get_something("x","y")
get_delta_by(hash1, hash2, &lambda_getter)
That last bit can be a one liner... but wrote it as two for clarity here.
In Ruby 2.3, you can use Hash#dig method, if it meets your needs.
hash1.dig("x", "y") - hash2.dig("x", "y")
#=> 8

How to count how many times an iterator will call yield?

I have a method, foo, that yields objects. I want to count the number of objects it yields.
I have
def total_foo
count = 0
foo { |f| count += 1}
count
end
but there's probably a better way. Any ideas for this new Rubyist?
Here's the definition for foo (it's a helper method in Rails):
def foo(resource=#resource)
resource.thingies.each do |thingy|
bar(thingy) { |b| yield b } # bar also yields objects
end
end
Any method that calls yield can be used to build an Enumerator object, on which you can call count, by means of the Object#to_enum method. Remember that when you call count the iterator is actually executed so it should be free of side effects! Following a runnable example that mimics your scenario:
#resources = [[1,2], [3,4]]
def foo(resources = #resources)
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo { |i| puts i }
# Output:
# 1
# 2
# 3
# 4
to_enum(:foo).count
# => 4
You can pass an argument to foo:
to_enum(:foo, [[5,6]]).count
# => 2
Alternatively you can define foo to return an Enumerator when it's called without a block, this is the way stdlib's iterators work:
def foo(resources = #resources)
return to_enum(__method__, resources) unless block_given?
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo.count
# => 4
foo([[1,2]]).count
# => 2
foo([[1,2]]) { |i| puts i }
# Output:
# 1
# 2
You can pass a block to to_enum that is called when you call size on the Enumerator to return a value:
def foo(resources = #resources)
unless block_given?
return to_enum(__method__, resources) do
resources.map(&:size).reduce(:+) # thanks to #Ajedi32
end
end
resources.each do |thingy|
thingy.each { |b| yield b }
end
end
foo.size
# => 4
foo([]).size
# => 0
In this case using size is sligthly faster than count, your mileage may vary.
Assuming you otherwise only care about the side-effect of foo, you could have foo itself count the iterations:
def foo(resource=#resource)
count = 0
resource.thingies.each do |thingy|
bar(thingy) do |b|
count += 1
yield b
end # bar also yields objects
end
count
end
And then:
count = foo { |f| whatever... }
You can also ignore the return value if you choose, so just:
foo { |f| whatever... }
In cases you don't care what the count is.
There may be better ways to handle all of this depending upon the bigger context.

How do I call a method, given its name, on an element of an array?

How do I call a method, given its name, on an element of an array?
For example, I could have:
thing = "each"
I want to be able to do something like:
def do_thing(thing)
array = [object1,object2]
array[0].thing
end
so that do_thing(to_s), for example, would run object1.to_s.
You can use public_send or send. public_send only sends to public methods while send can see public and private methods.
def do_thing(thing)
array = [1,2,3]
array.public_send(thing)
end
do_thing('first')
# => 1
do_thing(:last)
# => 3
Update A more general version:
def do_thing(array, index, method, *args)
array[index].public_send(method, *args)
end
do_thing([1, 2, 3], 0, :to_s)
# => "1"
do_thing([[1,2], [3, 4]], 0, :fetch, 0)
# => 1
require 'ostruct'
o = OpenStruct.new(attribute: 'foo')
do_thing([o], 0, :attribute=, 'bar')
o.attribute == 'bar'
# => true
Object#send
thing = "each"
def do_thing(thing)
array = [1,2,3]
array.send(thing)
end
From the doc:
class Klass
def hello(*args)
"Hello " + args.join(' ')
end
end
k = Klass.new
k.send :hello, "gentle", "readers" #=> "Hello gentle readers"
Here is an example to help you out although I don't have any idea what objects are residing inside your array:
arr = [Array.new(2,10),"abc" ]
arr.each{|i| p i.send(:length)}
#>>2
#>>3

How to handle combination []+= for auto-vivifying hash in Ruby?

In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }

Resources