I have a hash of dates:
values = {a: Time.now, b: Time.now - 3.days}
# => {:a=>2015-11-24 22:35:56 +0900, :b=>2015-11-21 22:35:56 +0900}
To select the values whose date is within one day from now, I wrote:
values.reject{|i, v| v < Time.now - 1.day}
# => {:a=>2015-11-24 22:35:56 +0900}
When I use i[1] instead of v, that doesn't work,
values.reject{|i| i[1] < Time.now - 1.day}
# => NoMethodError: undefined method `<' for nil:NilClass
while with map, it works.
values.map{|i| i[1]}
# => [2015-11-24 22:35:56 +0900, 2015-11-21 22:35:56 +0900]
Why does Array#reject behave differently?
http://ruby-doc.org/core-2.2.3/Hash.html#method-i-reject
Hash#reject yields the key and the value to the block. i is just the key. Your keys are symbols; presumably array accessor (i[1]) on symbols returns nil.
Hash#map isn't a thing, I guess. You're probably getting Enumerable#map. Hash is an Enumerable, so you can still call map on hashes.
http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-map
Enumerable#map is yielding each key-value pair from the hash as a 2-element array:[:a, 2015-11-24 22:35:56 +0900]
BTW in many languages hashes are called associative arrays. Rubyists usually call them hashes.
reject is a method on Hash, which takes a block with two block variables: the key and the value. If you pass it a block with only one variable like so:
some_hash.reject{|x| ...}
then x will be assigned the key, and its corresponding value is not accessible. Doing x[1] calls the symbol method [] on x, which gives nil because :a and :b are only one character long.
On the other hand, Enumerable#map implicitly casts the hash to an array, and takes a block with one block variable. If you actually pass it a block with one variable like so:
some_hash.map{|x| ...}
then x will be an array that has a key-value pair. Doing x[1] calls the array method [], and returns the value of the hash.
If you instead pass a block with two variables like so:
some_hash.map{|x, y| ...}
then destruction will be implicitly applied to adjust the number of variables, i.e., the live about would be interpreted as:
some_hash.map{|(x, y)| ...}
and x, y would restrictively be the key and the value.
Probably you were confused because Array#reject takes a block with only one variable, and works differently with Hash#reject.
Related
How to assign key value pairs to exiting hash? I have the following code and I want to append some key value pairs to result variable.
def extra_variables
result = ansible_vars_from_objects(#handle.object, {})
result = ansible_vars_from_options(result)
#handle.log(:info, "Extra vars is: #{result}")
ansible_vars_from_ws_values(result)
end
Here is the log output of the result variable:
[----] I, [2022-03-08T21:31:41.701307 #322:2acf0cb72fb8] INFO -- automation: Q-task_id([r345_miq_provision_1235]) <AEMethod launch_ansible_job> Extra vars is: {"ansible_ssh_user"=>"ubuntu"}
Use the Hash#merge! Method
There's a built-in method for merging a Hash object into another Hash in-place: Hash#merge!. The main caveat is that Hash objects must have unique keys, so keep this in mind if you're trying to merge objects with the same top-level keys because the last key in the insertion order wins.
For example:
hash = {a: 1, b:2}
other_hash = {c: 3}
hash.merge! other_hash
hash
#=> {:a=>1, :b=>2, :c=>3}
Watch Out for Parsing Issues When Merging Hash Literals
Also note that if you're trying to merge Hash literals, you'll need to enclose the Hash in parentheses so that the interpreter doesn't think you're trying to pass a block. For example, you'd need to use:
hash.merge!({d: 4})
to avoid Ruby thinkings {d: 4} was a block passed to #merge!, but so far as I know this isn't a problem in any currently-supported Ruby when using a variable as the argument to #merge!. However, it's something to keep in mind if you get an exception like:
syntax error, unexpected ':', expecting '}' (SyntaxError)
which is pretty uninformative, but as of Ruby 3.1.1 that's the exception raised by this particular parsing issue.
the following code return this error:
block in find_word_lengths': undefined method `[]=' for 3:Integer (NoMethodError)
animals = ['cat', 'horse', 'rabbit', 'deer']
def find_word_lengths(word_list)
word_list.reduce(Hash.new()) do |result, animal|
result[animal] = animal.length
end
end
puts find_word_lengths(animals)
The return value of the block is the accumulator value for the next iteration. That is how a fold works.
Assignments in Ruby evaluate to the right-hand side. So, in the first iteration of reduce, the block evaluates to 3 (the length of 'cat'). Which means that in the second iteration of reduce, result is 3, and you are essentially running
3['horse'] = 5
# which is equivalent to
3.[]=('horse', 5)
Which is why you are getting the error message that the Integer 3 does not respond to the message []=.
So, you need to make sure that your block always returns the value that you want to use for the accumulator in the next iteration. Something like this:
word_list.reduce(Hash.new()) do |result, animal|
result.tap {|result| result[animal] = animal.length }
end
This would be the obvious solution, although somewhat cheating.
word_list.reduce(Hash.new()) do |result, animal|
result.merge(animal => animal.length)
end
Would be more idiomatic.
However, when you want to fold into a mutable object, it makes more sense to use Enumerable#each_with_object instead of Enumerable#reduce. each_with_object ignores the result of the block, and simply passes the same object every time. Note that somewhat confusingly, the order of the block parameters is swapped in each_with_object compared to reduce.
word_list.each_with_object(Hash.new()) do |animal, result|
result[animal] = animal.length
end
But I guess the most idiomatic solution would be something like this:
word_list.map {|word| [word, word.length] }.to_h
By the way, in Ruby, it is idiomatic to leave out the parentheses for the argument list if you are not passing any arguments, so Hash.new() should be Hash.new instead. Even more important than being idiomatic is to be consistent – confusingly, you leave out the parentheses for animal.length, but not for Hash.new
Even more idiomatically, you would use the Hash literal notation instead of the Hash::new method, i.e. you should use {} instead of Hash.new.
Consider the following:
(1..10).inject{|memo, n| memo + n}
Question:
How does n know that it is supposed to store all the values from 1..10? I'm confused how Ruby is able to understand that n can automatically be associated with (1..10) right away, and memo is just memo.
I know Ruby code blocks aren't the same as the C or Java code blocks--Ruby code blocks work a bit differently. I'm confused as to how variables that are in between the upright pipes '|' will automatically be assigned to parts of an object. For example:
hash1 = {"a" => 111, "b" => 222}
hash2 = {"b" => 333, "c" => 444}
hash1.merge(hash2) {|key, old, new| old}
How do '|key, old, new|' automatically assign themselves in such a way such that when I type 'old' in the code block, it is automatically aware that 'old' refers to the older hash value? I never assigned 'old' to anything, just declared it. Can someone explain how this works?
The parameters for the block are determined by the method definition. The definition for reduce/inject is overloaded (docs) and defined in C, but if you wanted to define it, you could do it like so (note, this doesn't cover all the overloaded cases for the actual reduce definition):
module Enumerable
def my_reduce(memo=nil, &blk)
# if a starting memo is not given, it defaults to the first element
# in the list and that element is skipped for iteration
elements = memo ? self : self[1..-1]
memo ||= self[0]
elements.each { |element| memo = blk.call(memo, element) }
memo
end
end
This method definition determines what values to use for memo and element and calls the blk variable (a block passed to the method) with them in a specific order.
Note, however, that blocks are not like regular methods, because they don't check the number of arguments. For example: (note, this example shows the usage of yield which is another way to pass a block parameter)
def foo
yield 1
end
# The b and c variables here will be nil
foo { |a, b, c| [a,b,c].compact.sum } # => 1
You can also use deconstruction to define variables at the time you run the block, for example if you wanted to reduce over a hash you could do something like this:
# this just copies the hash
{a: 1}.reduce({}) { |memo, (key, val)| memo[key] = val; memo }
How this works is, calling reduce on a hash implicitly calls to_a, which converts it to a list of tuples (e.g. {a: 1}.to_a = [[:a, 1]]). reduce passes each tuple as the second argument to the block. In the place where the block is called, the tuple is deconstructed into separate key and value variables.
A code block is just a function with no name. Like any other function, it can be called multiple times with different arguments. If you have a method
def add(a, b)
a + b
end
How does add know that sometimes a is 5 and sometimes a is 7?
Enumerable#inject simply calls the function once for each element, passing the element as an argument.
It looks a bit like this:
module Enumerable
def inject(memo)
each do |el|
memo = yield memo, el
end
memo
end
end
And memo is just memo
what do you mean, "just memo"? memo and n take whatever values inject passes. And it is implemented to pass accumulator/memo as first argument and current collection element as second argument.
How do '|key, old, new|' automatically assign themselves
They don't "assign themselves". merge assigns them. Or rather, passes those values (key, old value, new value) in that order as block parameters.
If you instead write
hash1.merge(hash2) {|foo, bar, baz| bar}
It'll still work exactly as before. Parameter names mean nothing [here]. It's actual values that matter.
Just to simplify some of the other good answers here:
If you are struggling understanding blocks, an easy way to think of them is as a primitive and temporary method that you are creating and executing in place, and the values between the pipe characters |memo| is simply the argument signature.
There is no special special concept behind the arguments, they are simply there for the method you are invoking to pass a variable to, like calling any other method with an argument. Similar to a method, the arguments are "local" variables within the scope of the block (there are some nuances to this depending on the syntax you use to call the block, but I digress, that is another matter).
The method you pass the block to simply invokes this "temporary method" and passes the arguments to it that it is designed to do. Just like calling a method normally, with some slight differences, such as there are no "required" arguments. If you do not define any arguments to receive, it will happily just not pass them instead of raising an ArgumentError. Likewise, if you define too many arguments for the block to receive, they will simply be nil within the block, no errors for not being defined.
I'm trying to return a list of values based on user defined arguments, from hashes defined in the local environment.
def my_method *args
#initialize accumulator
accumulator = Hash.new(0)
#define hashes in local environment
foo=Hash["key1"=>["var1","var2"],"key2"=>["var3","var4","var5"]]
bar=Hash["key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"]]
baz=Hash["key6"=>["var13","var14","var15","var16"]]
#iterate over args and build accumulator
args.each do |x|
if foo.has_key?(x)
accumulator=foo.assoc(x)
elsif bar.has_key?(x)
accumulator=bar.assoc(x)
elsif baz.has_key?(x)
accumulator=baz.assoc(x)
else
puts "invalid input"
end
end
#convert accumulator to list, and return value
return accumulator = accumulator.to_a {|k,v| [k].product(v).flatten}
end
The user is to call the method with arguments that are keywords, and the function to return a list of values associated with each keyword received.
For instance
> my_method(key5,key6,key1)
=> ["var10","var11","var12","var13","var14","var15","var16","var1","var2"]
The output can be in any order. I received the following error when I tried to run the code:
undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)
Please would you point me how to troubleshoot this? In Terminal assoc performs exactly how I expect it to:
> foo.assoc("key1")
=> ["var1","var2"]
I'm guessing you're coming to Ruby from some other language, as there is a lot of unnecessary cruft in this method. Furthermore, it won't return what you expect for a variety of reasons.
`accumulator = Hash.new(0)`
This is unnecessary, as (1), you're expecting an array to be returned, and (2), you don't need to pre-initialize variables in ruby.
The Hash[...] syntax is unconventional in this context, and is typically used to convert some other enumerable (usually an array) into a hash, as in Hash[1,2,3,4] #=> { 1 => 2, 3 => 4}. When you're defining a hash, you can just use the curly brackets { ... }.
For every iteration of args, you're assigning accumulator to the result of the hash lookup instead of accumulating values (which, based on your example output, is what you need to do). Instead, you should be looking at various array concatenation methods like push, +=, <<, etc.
As it looks like you don't need the keys in the result, assoc is probably overkill. You would be better served with fetch or simple bracket lookup (hash[key]).
Finally, while you can call any method in Ruby with a block, as you've done with to_a, unless the method specifically yields a value to the block, Ruby will ignore it, so [k].product(v).flatten isn't actually doing anything.
I don't mean to be too critical - Ruby's syntax is extremely flexible but also relatively compact compared to other languages, which means it's easy to take it too far and end up with hard to understand and hard to maintain methods.
There is another side effect of how your method is constructed wherein the accumulator will only collect the values from the first hash that has a particular key, even if more than one hash has that key. Since I don't know if that's intentional or not, I'll preserve this functionality.
Here is a version of your method that returns what you expect:
def my_method(*args)
foo = { "key1"=>["var1","var2"],"key2"=>["var3","var4","var5"] }
bar = { "key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"] }
baz = { "key6"=>["var13","var14","var15","var16"] }
merged = [foo, bar, baz].reverse.inject({}, :merge)
args.inject([]) do |array, key|
array += Array(merged[key])
end
end
In general, I wouldn't define a method with built-in data, but I'm going to leave it in to be closer to your original method. Hash#merge combines two hashes and overwrites any duplicate keys in the original hash with those in the argument hash. The Array() call coerces an array even when the key is not present, so you don't need to explicitly handle that error.
I would encourage you to look up the inject method - it's quite versatile and is useful in many situations. inject uses its own accumulator variable (optionally defined as an argument) which is yielded to the block as the first block parameter.
I've recently started coding Ruby and I'm having a mis-understanding with block parameters.
Take the following code for example:
h = { # A hash that maps number names to digits
:one => 1, # The "arrows" show mappings: key=>value
:two => 2 # The colons indicate Symbol literals
}
h[:one] # => 1. Access a value by key
h[:three] = 3 # Add a new key/value pair to the hash
h.each do |key,value| # Iterate through the key/value pairs
print "#{value}:#{key}; " # Note variables substituted into string
end # Prints "1:one; 2:two; 3:three; "
I understand the general hash functionality, however I don't understand how value and key are set to anything. They are specified as parameters in the block, but the hash is never associated in any way with these parameters.
This is the Ruby block (Ruby's name for an anonymous function) syntax. And key, value are nothing but the arguments passed to the anonymous function.
Hash#each takes one parameter: A function which has 2 parameters, key and value.
So if we break it down into parts, this part of your code: h.each, is calling the each function on h. And this part of your code:
do |key, value| # Iterate through the key/value pairs
print "#{value}:#{key}; " # Note variables substituted into string
end # Prints "1:one; 2:two; 3:three;
is the function passed to each as an argument and key, value are arguments passed to this function. It doesn't matter what you name them, first argument expected is key and second argument expected is value.
Lets draw some analogies. Consider a basic function:
def name_of_function(arg1, arg1)
# Do stuff
end
# You'd call it such:
foo.name_of_function bar, baz # bar is becomes as arg1, baz becomes arg2
# As a block:
ooga = lambda { |arg1, arg2|
# Do stuff
}
# Note that this is exactly same as:
ooga = lambda do |arg1, arg2|
# Do stuff
end
# You would call it such:
ooga.call(bar, baz) # bar is becomes as arg1, baz becomes arg2
So your code can also be written as:
print_key_value = lambda{|arg1, arg2| print "#{arg1}:#{arg2}"}
h = {
:one => 1,
:two => 2
}
h.each &print_key_value
There are multiple ways in which the code inside a block can be executed:
yield
yield key, value # This is one possible way in which Hash#each can use a block
yield item
block.call
block.call(key, value) # This is another way in which Hash#each can use a block
block.call(item)
The hash (h) is associated with the loop due to you calling h.each rather than calling each on something else. It's effectively saying, "For each key/value pair in h, let the key iteration variable be the key, let the value iteration variable be the value, then execute the body of the loop."
If that doesn't help, have a look at this page on each... and if you can explain more about which bit you're finding tricky, we may be able to help more. (Well, others may be able to. I don't really know Ruby.)
The hash is indeed associated with these parameters because you call h.each to iterate over the hash:
h.each <- here's the link you are missing
Perhaps it's easier for you if you start with an array instead:
a = [1,2,3]
a.each do |v|
puts v
end
and play around with this first (each, each_with_index, ...)
when you call h.each, that's when you say that this is this specific h hash that you want to use for this each iteration.
Hence when you do that the value and key variables are assigned to the values in your hash, one by one.
I think the question is about the variable names. The names have no significance. Only the order matters. Within |...| inside each {...}, the key and the value are given in that order. Since its natural to assign the variable name key to key and value to value, you often find it done like that. In fact, it can be anything else.
each{|k, v| ...} # k is key, v is value
each{|a, b| ...} # a is key, b is value
or even misleadingly:
each{|value, key| ...} # value is key, key is value