Changing a few values in a hash - ruby

Say I have a hash:
h = {"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 }
and I want to get:
{"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
so I create a method that takes a hash:
def edge_adjust(hash)
hash["upper_left", nil, "upper_right", nil]
end
but I get the error:
wrong number of arguments (4 for 1)
I know it's giving the elements of the hash one at a time or my method is broke, not sure how to get what I want.

You may want to use merge method to replace first hash values with the values from the second hash:
def edge_adjust(hash)
hash.merge( {"upper_left", nil, "upper_right", nil})
end
edge_adjust({"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
Please not that if first hash does not contain some values from the second hash then these values will be created:
edge_adjust({"lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 } as well

Your Hash initialization is wrong. I suppose you want something like:
h = Hash["upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4]
["upper_left", "upper_right"].each{|k| h[k] = nil}

In this case, Hash#[] is an accessor method, not something that will modify the data. It takes only one argument, the key, and will return the value stored in that location, if any. This is not to be confused with Hash.[] which is a class method to create new hashes.
If you want to mass-assign values to the hash, you have a few options, but the most straight-forward is:
# Spin through a list of keys to remove...
%w[ upper_left upper_right ].each do |k|
# ...and nil out each entry.
h[k] = nil
end
You might also try and use a pattern to zap out any entries you don't want:
# Delete all keys that begin with "upper_"
h.delete_if { |k| k.match(/^upper_/) }
Note that this actually deletes the keys as well, so you can still get nil when fetching, but they are not present in h.keys.

Related

How use dig method several for children object

I have an hash
{:result=>
{:"1"=>
[{:"1"=>1,
:"2"=>"4698192612070913717",
:"5"=>
{:"1"=>{:"1"=>"1.0.0"},
:"2"=>
{:"1"=>1,
:"2"=>"1525341956127",
:"3"=>1000000000,
:"4"=>0,
:"5"=>{:"1"=>1000000000}},
:"3"=>["17"],
:"6"=>"4704522736971289334",
:"8"=>["4618851880555471022"],
:"9"=>[1]},
:"6"=>{:"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"7"=>{:"1"=>1},
:"8"=>"production"},
{:"1"=>4,
:"2"=>"4700283765268993541",
:"6"=>{:"2"=>{:"1"=>200}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"beta"},
{:"1"=>5,
:"2"=>"4699074054925986704",
:"6"=>{:"2"=>{:"1"=>100}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"alpha"},
{:"1"=>10,
:"2"=>"4697702456121346981",
:"6"=>{:"2"=>{:"1"=>50}, :"3"=>{:"2"=>false}, :"4"=>{:"1"=>false}},
:"8"=>"internal"}],
:"3"=>{:"1"=>true, :"2"=>{:"1"=>{:"1"=>false}, :"2"=>{:"1"=>false}}},
:"4"=>false},
:xsrf=>"AMtNNDFJl06mR54j2zxFjYIYfGQR22sUKA:1528830206790"}
I am looking for simple way to return a value or nil
I have tried this
result[:'result'][:'1'][1].dig(:'5').dig(:'1').dig(:'1')
but it's not working
What can I do to avoid this
if result[:'result'][:'1'][1].dig(:'5')
puts result[:'result'][:'1'][1][:'5'][:'1'][:'1']
end
The idea behind dig is that you can go several levels deep into a hash at the same time and return nil if the key doesn't exist at any level during the 'digging'. So result[:'result'][:'1'][1].dig(:'5', :'1', :'1') will do what you are looking for and clean up your code as well. In fact, you could make it a little safer if you wanted by doing result.dig(:result, :'1', 1, :'5', :'1', :'1')
dig is not a single method, but a family of four methods, all of which made their debut in Ruby v2.3: Array#dig, Hash#dig, Struct#dig and OpenStruct#dig.
For example,
h = { a: [1, { c: 2, d: 3 }], b: 2 }
h.dig(:a, 1, :d)
#=> 3
employs Hash#dig because dig's receiver is a hash. Moreover, one might expect that when, in an intermediate calculation, dig has unearthed [1, { c: 2, d: 3 }] it will pass the shovel to Array#dig for further excavation.
Suppose
h = { a: [1, 2] }
Then
h.dig(:a, 1) #=> 2
h.dig(:a).dig(1) #=> 2
Does that mean the two are equivalent? Try this:
h.dig('cat', 1) #=> nil
h.dig('cat').dig(1) #=> NoMethodError: undefined method `dig' for nil:NilClass
The exception is due to the fact that h.dig('cat') #=> nil and NilClass has no instance method dig, so nil.dig(1) raises the exception. No, the two expressions are not equivalent.
If the value of the variable result is the OPs hash, we have (as pointed out by #Isaiah) the following.
result.dig(:result, :'1', 0, :"5", :"1", :"1")
#=> "1.0.0"
result.dig(:result, :'1', 0, :cat, :"1", :"1")
#=> nil
Note that dig will still raise an exception if the wrong data type is used:
[1, 2].dig(:a)
#=> TypeError: no implicit conversion of Symbol into Integer
To support versions of Ruby prior to 2.3 (where dig is not available) we can write the following, using Enumerable#reduce (aka inject).
arr = [:result, :'1', 0, :"5", :"1", :"1"]
arr.reduce(result) { |memo, obj| memo && memo[obj] }
#=> "1.0.0"
arr = [:result, :'1', 0, :cat, :"1", :"1"]
arr.reduce(result) { |memo, obj| memo && memo[obj] }
#=> nil

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

What is meant: "Hash.new takes a default value for the hash, which is the value of the hash for a nonexistent key"

I'm currently going through the Ruby on Rails tutorial by Michael Hartl
Not understanding the meaning of this statement found in section 4.4.1:
Hashes, in contrast, are different. While the array constructor
Array.new takes an initial value for the array, Hash.new takes a
default value for the hash, which is the value of the hash for a
nonexistent key:
Could someone help explain what is meant by this? I don't understand what the author is trying to get at regarding how hashes differ from arrays in the context of this section of the book
You can always try out the code in irb or rails console to find out what they mean.
Array.new
# => []
Array.new(7)
# => [nil, nil, nil, nil, nil, nil, nil]
h1 = Hash.new
h1['abc']
# => nil
h2 = Hash.new(7)
h2['abc']
# => 7
Arrays and hashes both have a constructor method that takes a value. What this value is used for is different between the two.
For arrays, the value is used to initialize the array (example taken from mentioned tutorial):
a = Array.new([1, 3, 2])
# `a` is equal to [1, 3, 2]
Unlike arrays, the new constructor for hashes doesn't use its passed arguments to initialize the hash. So, for example, typing h = Hash.new('a', 1) does not initialize the hash with a (key, value) pair of a and 1:
h = Hash.new('a', 1) # NO. Does not give you { 'a' => 1 }!
Instead, passing a value to Hash.new causes the hash to use that value as a default when a non-existent key is passed. Normally, hashes return nil for non-existent keys, but by passing a default value, you can have hashes return the default in those cases:
nilHash = { 'x' => 5 }
nilHash['x'] # Return 5, because the key 'x' exists in nilHash
nilHash['foo'] # Returns nil, because there is no key 'foo' in nilHash
defaultHash = Hash.new(100)
defaultHash['x'] = 5
defaultHash['x'] # Return 5, because the key 'x' exists in defaultHash
defaultHash['foo']
# Returns 100 instead of nil, because you passed 100
# as the default value for non-existent keys for this hash
Begin by reading the docs for the class method Hash#new. You will see there are three forms:
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
Creating an Empty Hash
The first form is used to create an empty hash:
h = Hash.new #=> {}
which is more commonly written:
h = {} #=> {}
The other two ways of creating a hash with Hash#new establish a default value for a key/value pair when the hash does not already contain the key.
Hash.new with an argument
You can create a hash with a default value in one of two ways:
Hash.new(<default value>)
or
h = Hash.new # or h = {}
h.default = <default value>
Suppose the default value for the hash were 4; that is:
h = Hash.new(4) #=> {}
h[:pop] = 7 #=> 7
h[:pop] += 1 #=> 8
h[:pop] #=> 8
h #=> {:pop=>8}
h[:chips] #=> 4
h #=> {:pop=>8}
h[:chips] += 1 #=> 5
h #=> {:pop=>8, :chips=>5}
h[:chips] #=> 5
Notice that the default value does not affect the value of :pop. That's because it was created with an assignment:
h[:pop] = 7
h[:chips] by itself merely returns the default value (4); it does not add the key/value pair :chips=>4 to the hash! I repeat: it does not add the key/value pair to the hash. That's important!
h[:chips] += 1
is shorthand for:
h[:chips] = h[:chips] + 1
Since the hash h does not have a key :chips when h[:chips] on the right side of the equals sign is evaluated, it returns the default value of 4, then 1 is added to make it 5 and that value is assigned to h[:chips], which adds the key value pair :chips=>5 to the hash, as seen in following line. The last line merely reports the value for the existing key :chips.
So why would you want to establish a default value? I would venture that the main reason is to be able to initialize it with zero, so you can use:
h[k] += 1
instead of
k[k] = (h.key?(k)) ? h[k] + 1 : 1
or the trick:
h[k] = (h[k] ||= 0) + 1
(which only works when hash values are intended to be non-nil). Incidentally, key? is aka has_key?.
Can we make the default a string instead? Of course:
h = Hash.new('magpie')
h[:bluebird] #=> "magpie"
h #=> {}
h[:bluebird] = h[:bluebird] #=> "magpie"
h #=> {:bluebird=>"magpie"}
h[:redbird] = h[:redbird] #=> "magpie"
h #=> {:bluebird=>"magpie", :redbird=>"magpie"}
h[:bluebird] << "jay" #=> "magpiejay"
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay"}
You may be scratching your head over the last line: why did h[:bluebird] << "jay" cause h[:redbird] to change?? Perhaps this will explain what's going on here:
h[:robin] #=> "magpiejay"
h[:robin].object_id #=> 2156227520
h[:bluebird].object_id #=> 2156227520
h[:redbird].object_id #=> 2156227520
h[:robin] merely returns the default value, which we see has been changed from "magpie" to "magpiejay". Now look at the object_id's for the default value and for the values associated with the keys :bluebird and :redbird. As you see, all values are the same object, so if we change one, we change all the the others, including the default value. It is now evident why h[:bluebird] << "jay" changed the default value.
We can clarify this further by adding a stately eagle:
h[:eagle] #=> "magpiejay"
h[:eagle] += "starling" #=> "magpiejaystarling"
h[:eagle].object_id #=> 2157098780
h #=> {:bluebird=>"magpiejay", :redbird=>"magpiejay", :eagle=>"magpiejaystarling"}
Because
h[:eagle] += "starling" #=> "magpiejaystarling"
is equivalent to:
h[:eagle] = h[:eagle] + "starling"
we have created a new object on the right side of the equals sign and assigned it to h[:eagle]. That's why the values for the keys :bluebird and :redbird are unaffected and h[:eagle] has a different object_id.
We have the similar problems if we write: Hash.new([]) or Hash.new({}). If there are ever reasons to use those defaults, I'm not aware of them. It certainly can be very useful for the default value to be an empty string, array or hash, but for that you need the third form of Hash.new, which takes a block.
Hash.new with a block
We now consider the third and final version of Hash#new, which takes a block, like so:
Hash.new { |h,k| ??? }
You may be expecting this to be devilishly complex and subtle, certainly much harder to grasp than the other two forms of the method. If so, you'd be wrong. It's actually quite simple, if you think of it as looking like this:
Hash.new { |h,k| h[k] = ??? }
In other words, Ruby is saying to you, "The hash h doesn't have the key k. What would you like it's value to be? Now consider the following:
h7 = Hash.new { |h,k| h[k]=7 }
hs = Hash.new { |h,k| h[k]='cat' }
ha = Hash.new { |h,k| h[k]=[] }
hh = Hash.new { |h,k| h[k]={} }
h7[:a] += 3 #=> 10
hs[:b] << 'nip' #=> "catnip"
ha[:c] << 4 << 6 #=> [4, 6]
ha[:d] << 7 #=> [7]
ha #=> {:c=>[4, 6], :d=>[7]}
hh[:k].merge({b: 4}) #=> {:b=>4}
hh #=> {}
hh[:k].merge!({b: 4} ) #=> {:b=>4}
hh #=> {:k=>{:b=>4}}
Notice that you cannot write ha = Hash.new { |h,k| [] } (or equivalently, ha = Hash.new { [] }) and expect h[k] => [] to be added to the hash. You can do whatever you like within the block; you are neither required nor limited to specifying a value for the key. In effect, within the block Ruby is actually saying, "A key that is not in the hash has been referenced without a value. I'm giving you that reference and also a reference to the hash. That will allow you to add that key with a value to the hash, if that's what you want to do, but what you do in this block is entirely your business."
The default values for the hashes h7, hs, ha and hh are respectively the number 7 (though it would be easier to simply enter 7 as An argument), an empty string, an empty array or an empty hash. Probably the last two are the most common use of Hash#new with a block, as in:
array = [[:a, 1], [:b, 3], [:a, 4], [:b, 6]]
array.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v),h| h[k] << v }
#=> {:a=>[1, 4], :b=>[3, 6]}
That's really about all there is to the last form of Hash#new.

Why does .map produce a row of nils when used to enumerate over hashes?

test =
{:content=>"type_name", :content_length=>9, :array_index=>0},
{:content=>"product_id", :content_length=>10, :array_index=>1},
{:content=>"First Item", :content_length=>10, :array_index=>0},
{:content=>"1111", :content_length=>4, :array_index=>1}
pp test.map {|x| puts x} #=>
{:content=>"type_name", :content_length=>9, :array_index=>0}
{:content=>"product_id", :content_length=>10, :array_index=>1}
{:content=>"First Item", :content_length=>10, :array_index=>0}
{:content=>"1111", :content_length=>4, :array_index=>1}
[nil, nil, nil, nil]
What is the cause of that array of nils? The map works perfectly, but then it causes these nils!
The trouble is that #map is designed to transform an array into a different array. Generally, the block of #map will not have side effects. Here's a use of #map to double all the numbers in an array:
[1, 2, 3].map { |n| n * 2} # => [2, 4, 6]
If the purpose of your loop is solely to have side effects (such as printing the elements), you want #each instead:
[1, 2, 3].each { |n| puts n }
# => 1
# => 2
# => 3
In this case, we don't care about the return value of #each. All we care about is that each number gets printed.
Argh what a stupid error!
This fixes it:
test.map {|x| puts x}
I was pretty printing the puts statement, and irb, trying to be helpful, returned nil four times!

Arrays misbehaving

Here's the code:
# a = Array.new(3, Array.new(3))
a = [[nil,nil,nil],[nil,nil,nil]]
a[0][0] = 1
a.each {|line| p line}
With the output:
[1, nil, nil]
[nil, nil, nil]
but using the commented line:
[1, nil, nil]
[1, nil, nil]
[1, nil, nil]
So why is that?
The commented line is assigning three of the same reference to the array, so a change to one array will propagate across the other references to it.
As for the 2 arrays vs 3, that's simply a matter of the first line specifying 3 as its first parameter and only specifying 2 array literals in the second line.
To create the nested arrays without having any shared references:
a = Array.new(3) {Array.new(3)}
When passed a block ({...} or do ... end), Array.new will call the block to obtain the value of each element of the array.

Resources