Destructure a Hash in block arguments in Ruby 2.7 - ruby

This:
[{a: 1, b: 2}, {a: 3, b: 4}].each do |a:, b:| p a end
Raises the following warning in Ruby 2.7
warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
I understand that each is passing a hash to the block, and the block now accepts |a:, b:| as named arguments but, is there any way to correctly destructure the hash in this context?

I'm uncertain, but I think perhaps the idea is to use pattern matching for hash destructuring? For example:
{a: 1, b: 2}.tap do |args|
args in {a: a, b: b} # !!!
p a
end
Currently by default however, this will display a warning (which can be disabled):
Pattern matching is experimental, and the behavior may change in future versions of Ruby!

If you already know that you have two keys in each Hash as per your example, why not this?
[{a: 1, b: 2}, {a: 3, b: 4}].each do |h|
a, b = h.values
p a
end

In Ruby 3 you can use the rightward assignment operator =>:
{a: 1, b: 2}.tap do |args|
args => { a:, b: }
p a
end

Related

How do I find the value of the key that I set with argument in Ruby?

I am currently working on a project with Ruby.
I don't know how to find the set of the value and key that has the same key as I gave with argument.
what I have tried is something like this below.
but it doesn't work.
def find_target(type)
target = type_list.find{|k,v| k == type}
target
end
def type_list
type = {
a: 1,
b: 2,
c: 3,
d: 4
}
type
end
but instead of giving an argument of variable, I gave a string as an argument, and it worked.
def find_target(a)
target = type_list.find{|k,v| k == a}
target
end
Edited
What I really want find_target to do is returning a matched value.
For example, when an argument is a, then it returns 1.
How can I solve this?
I would love you if you could help me.
Thank you .
I think one thing tripping you up is that your type_list hash is keyed with symbols (rather than strings or the value of a variable). The syntax you're using:
{a: 1}
is just shorthand for this:
{:a => 1}
Which means "A Hash with one key: the symbol :a with the value 1". That's distinct from:
{'a' => 1} # Keyed with the string 'a'
and this:
a = 'something'
b = {a => 1} # Keyed with value of 'a' at the time of creating, ie: {'something' => 1}. Note that if you change the value of a, the hash key won't change.
What do you expect as your return value from find_target(:a)? The find method on a Hash will return an Enumerator - mostly equivalent to a two-element Array, with the key and the value: {a: 1}.find{|k,v|k == :a} will return [:a, 1]. Is that what you want?
If you just want to have the value 1 returned, then you're really doing a hash lookup, and you don't need any extra methods at all. A common way to do this would be to define type_list as a constant, and then just refer to it by key:
TYPE_LIST = {
a: 1,
b: 2,
c: 3,
d: 4
}
#Then to find the type:
TYPE_LIST[:a] # Returns '1'
You might want to use a find_type method to handle the case where the key doesn't match a type: a plain Hash lookup will return nil, which you might not want.
Hope this helps put you on the right path, but I'm happy to expand this answer if needed!

How to prevent auto expansion of hash arguments?

Why does Ruby 2.3.1 auto-expand my hash argument when I don't provide an optional named argument (:to) and how do I prevent that?
def qsend(*args, to: nil, **opts)
puts "args: #{args}, opts: #{opts}"
end
qsend({ a: 1, b: 2, c: 3 })
qsend({ a: 1, b: 2, c: 3, d: 4 }, to: "me")
Output:
args: [], opts: {:a=>1, :b=>2, :c=>3}
args: [{:a=>1, :b=>2, :c=>3, :d=>4}], opts: {}
When I remove args from the argument list, my hash is not dropped into opts in the 2nd call, but an error is raised: "wrong number of arguments (given 1, expected 0) (ArgumentError)". And when I remove opts from the argument list, my hash is not dropped into args in the 1st call - an error is being thrown again, this time: "unknown keywords: a, b, c (ArgumentError)". Using args = {} instead of *args makes no difference.
The following call works (hash in 1st call will be dropped into args), but is there no better way?
qsend({ a: 1, b: 2, c: 3 }, {})
I would prefer to solve the problem in the method definition.
edit: method renamed, output fixed.

How to use &proc argument inside method

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

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.

Resources