How to use &proc argument inside method - ruby

Array#max_by returns only a single value, but I want to have all values that have the max value.
hashes = [{a: 1, b:2}, {a:2, b:3}, {a:1, b:3}]
max = hashes.map{|h| h[:b]}.max
hashes.select{|h| h[:b] == max}
# => [{a: 2, b: 3}, {a: 1, b: 3}]
This code works fine, and I want to add it to Array class.
class Array
def max_values_by(&proc)
max = map(&proc).max
# I don't know how to use `select` here.
end
end
How to access the value of the &proc argument?

Use the proc in the block passed to select by calling it with call:
class Array
def max_values_by(&proc)
max = map(&proc).max
select { |h| proc.call(h) == max }
end
end
hashes.max_values_by { |h| h[:b] }
=> [{a: 2, b: 3}, {a: 1, b: 3}]
or with yield, which gives identical results:
def max_values_by(&proc)
max = map(&proc).max
select { |h| yield(h) == max }
end
Although proc.call is a little longer than yield, I prefer it in this case because it makes it clearer that the same block is being used in two places in the method, and because it's weird to use both the implicit block passing of yield and the explicit passing of &proc in the same method.

#DaveSchweisguth suggests a great implementation using select, like you requested. Another way of achieving the same result is by using group_by, like this:
>> hashes.group_by{|h| h[:b]}.max.last
=> [{:a=>2, :b=>3}, {:a=>1, :b=>3}]
or monkey-patched into Array as:
class Array
def max_values_by(&proc)
group_by(&proc).max.last
end
end

Related

How to get reference to object you're calling in method?

I am trying to create a method for objects that I create. In this case it's an extension of the Array class. The method below, my_uniq, works. When I call puts [1, 1, 2, 6, 8, 8, 9].my_uniq.to_s it outputs to [1, 2, 6, 8].
In a similar manner, in median, I'm trying to get the reference of the object itself and then manipulate that data. So far I can only think of using the map function to assign a variable arr as an array to manipulate that data from.
Is there any method that you can call that gets the reference to what you're trying to manipulate? Example pseudo-code that I could replace arr = map {|n| n} with something like: arr = self.
class Array
def my_uniq
hash = {}
each do |num|
hash[num] = 0;
end
hash.keys
end
end
class Array
def median
arr = map {|n| n}
puts arr.to_s
end
end
Thanks in advance!
dup
class Array
def new_self
dup
end
def plus_one
arr = dup
arr.map! { |i| i + 1 }
end
def plus_one!
arr = self
arr.map! { |i| i + 1 }
end
end
array = [1, 3, 5]
array.new_self # => [1, 3, 5]
array.plus_one # => [2, 4, 6]
array # => [1, 3, 5]
array.plus_one! # => [2, 4, 6]
array # => [2, 4, 6]
dup makes a copy of the object, making it a safer choice if you need to manipulate data without mutating the original object. You could use self i.e. arr = self, but anything you do that changes arr will also change the value of self. It's a good idea to just use dup.
If you do want to manipulate and change the original object, then you can use self instead of dup, but you should make it a "bang" ! method. It is a convention in ruby to put a bang ! at the end of a method name if it mutates the receiving object. This is particularly important if other developers might use your code. Most Ruby developers would be very surprised if a non-bang method mutated the receiving object.
class Array
def median
arr = self
puts arr.to_s
end
end
[1,2,3].median # => [1,2,3]

is it possible to use hash keys as a first level variables inside ruby method?

I'll try to explain myself a little bit more
# Lets say I have this hash
options = {a: 1, b: 2}
# and here I'm calling the method
some_method(options)
def some_method(options)
# now instead of using options[:a] I'd like to simply use a.
options.delete_nesting_and_create_vars
a + b # :a + :b also good.
thanks!
Is it possible using Ruby2 splat parameters:
options = {a: 1, b: 2}
def some_method1(a:, b:)
a + b
end
or:
def some_method2(**options)
options[:a] + options[:b]
end
some_method1 **options
#⇒ 3
some_method2 **options
#⇒ 3
If your options are fixed, like only :a and :b are the only keys, you can write the method like this:
def some_method(a:, b:)
a + b
end
options = {a: 1, b: 2}
some_method(options) #=> 3

Apply something to certain value of a hash, return the whole hash

What is the proper way of doing so in Ruby in a functional and immutable way:
a = { price: 100, foo: :bar, bar: :baz }
def reduced_price(row)
row.merge(price: row[:price] / 2)
end
reduced_price(a) # => { price: 50, foo: :bar, bar: :baz }
I don't want to mutate anything and I don't like the consctruction row.merge(key: row[:key]) because it repeats the :key and refers to row twice. If there would be something like:
{a: 1, b: 2}.apply_to_key(:a) { |x| x * 10 } # => {a: 10, b: 2}
it would be great.
To sum up, I want a method that, when given a key, updates a single value of a hash by that key using the previous value, and then returns the whole hash.
And while I was writing the question, I have found the answer. Since SO suggests sharing my knowledge Q&A-style, here it is:
{a: 1, b: 2}.merge(a: nil) { |_, v| v * 10 } # => {a: 10, b: 2}
When you pass a block to hash.merge(other_hash), it will be executed for each pair of other_hash with parameters key, oldval, newval and should return resulting value. In a case above, my other_hash contains only one key I wanted to alter, and from the three params I care only for the second, old_val, to use it in my block.
It's perfectly chainable and doesn't mutate a thing. It has a bit of redundancy (having to pass nil or any other value to other_hash, having to ignore first parameter of the block), but I guess it's the closest I could get.
If you can suggest better answer, I will consider accepting it.

How to display dynamic case statement in Ruby

How would I write a case statement that would list all elements in an array, allow the user to pick one, and do processing on that element?
I have an array:
array = [ 'a', 'b', 'c', 'd' ]
Ultimately I'd like it to behave like this:
Choices:
1) a
2) b
3) c
4) d
Choice =>
After the user picks 3, I would then do processing based off the choice of the user. I can do it in bash pretty easily.
Ruby has no built-in menu stuff like shell scripting languages do. When doing menus, I favor constructing a hash of possible options and operating on that:
def array_to_menu_hash arr
Hash[arr.each_with_index.map { |e, i| [i+1, e] }]
end
def print_menu menu_hash
puts 'Choices:'
menu_hash.each { |k,v| puts "#{k}) #{v}" }
puts
end
def get_user_menu_choice menu_hash
print 'Choice => '
number = STDIN.gets.strip.to_i
menu_hash.fetch(number, nil)
end
def show_menu menu_hash
print_menu menu_hash
get_user_menu_choice menu_hash
end
def user_menu_choice choice_array
until choice = show_menu(array_to_menu_hash(choice_array)); end
choice
end
array = %w{a b c d}
choice = user_menu_choice(array)
puts "User choice was #{choice}"
The magic happens in array_to_menu_hash:
The [] method of Hash converts an array with the form [ [1, 2], [3, 4] ] to a hash {1 => 2, 3 => 4}. To get this array, we first call each_with_index on the original menu choice array. This returns an Enumerator that emits [element, index_number] when iterated. There are two problems with this Enumerator: the first is that Hash[] needs an array, not an Enumerator. The second is that the arrays emitted by the Enumerator have the elements in the wrong order (we need [index_number, element]). Both of these problems are solved with #map. This converts the Enumerator from each_with_index into an array of arrays, and the block given to it allows us to alter the result. In this case, we are adding one to the zero-based index and reversing the order of the sub-arrays.

simple hash merge by array of keys and values in ruby (with perl example)

In Perl to perform a hash update based on arrays of keys and values I can do something like:
#hash{'key1','key2','key3'} = ('val1','val2','val3');
In Ruby I could do something similar in a more complicated way:
hash.merge!(Hash[ *[['key1','key2','key3'],['val1','val2','val3']].transpose ])
OK but I doubt the effectivity of such procedure.
Now I would like to do a more complex assignment in a single line.
Perl example:
(#hash{'key1','key2','key3'}, $key4) = &some_function();
I have no idea if such a thing is possible in some simple Ruby way. Any hints?
For the Perl impaired, #hash{'key1','key2','key3'} = ('a', 'b', 'c') is a hash slice and is a shorthand for something like this:
$hash{'key1'} = 'a';
$hash{'key2'} = 'b';
$hash{'key3'} = 'c';
In Ruby 1.9 Hash.[] can take as its argument an array of two-valued arrays (in addition to the old behavior of a flat list of alternative key/value arguments). So it's relatively simple to do:
mash.merge!( Hash[ keys.zip(values) ] )
I do not know perl, so I'm not sure what your final "more complex assignment" is trying to do. Can you explain in words—or with the sample input and output—what you are trying to achieve?
Edit: based on the discussion in #fl00r's answer, you can do this:
def f(n)
# return n arguments
(1..n).to_a
end
h = {}
keys = [:a,:b,:c]
*vals, last = f(4)
h.merge!( Hash[ keys.zip(vals) ] )
p vals, last, h
#=> [1, 2, 3]
#=> 4
#=> {:a=>1, :b=>2, :c=>3}
The code *a, b = some_array will assign the last element to b and create a as an array of the other values. This syntax requires Ruby 1.9. If you require 1.8 compatibility, you can do:
vals = f(4)
last = vals.pop
h.merge!( Hash[ *keys.zip(vals).flatten ] )
You could redefine []= to support this:
class Hash
def []=(*args)
*keys, vals = args # if this doesn't work in your version of ruby, use "keys, vals = args[0...-1], args.last"
merge! Hash[keys.zip(vals.respond_to?(:each) ? vals : [vals])]
end
end
Now use
myhash[:key1, :key2, :key3] = :val1, :val2, :val3
# or
myhash[:key1, :key2, :key3] = some_method_returning_three_values
# or even
*myhash[:key1, :key2, :key3], local_var = some_method_returning_four_values
you can do this
def some_method
# some code that return this:
[{:key1 => 1, :key2 => 2, :key3 => 3}, 145]
end
hash, key = some_method
puts hash
#=> {:key1 => 1, :key2 => 2, :key3 => 3}
puts key
#=> 145
UPD
In Ruby you can do "parallel assignment", but you can't use hashes like you do in Perl (hash{:a, :b, :c)). But you can try this:
hash[:key1], hash[:key2], hash[:key3], key4 = some_method
where some_method returns an Array with 4 elements.

Resources