Set hash value at arbitrary location - ruby

I am working on an application where I'd like to modify part of an existing hash that looks like:
{a: {b: {c: 23}}}
to be:
{a: {b: {c: [23]}}}
However, the exact key to be set is dynamic and at an unknown depth in the hash. Is there a way to set a value in a hash given an array of keys? I'm hoping for something like:
my_hash['a','b','c'] = new_value
Getting a value from an arbitrary depth is straightforward via recursion, but since the traversal works on copies of the data, rather than references, I don't know a way to set the value without rebuilding the entire array during traversal.

except for the syntax (my_hash['a','b','c']), the following will do what you want
h = {a: {b: {c: { e: 23}}, d: 34}}
keys = ['a','b','c']
def replace_nested_value_by(h, keys, value)
if keys.size > 1
replace_nested_value_by(h[keys.first.to_sym], keys[1..-1], value)
elsif keys.size == 1
h[keys.first.to_sym] = value
end
end
puts h
replace_nested_value_by(h, keys, 42)
puts h

As an addition to toch's answer and just a bit beyond comment scope, I'll also suggest that this can be a good place to use inject:
def nested_replace(hash, *keys, last_key, value)
result = keys.inject(hash) { |r, k| r[k] }
result[last_key] = value
end
h = {a: {b: {c: [23]}}}
nested_replace h, :a, :b, :c, 42
puts h
# => {:a=>{:b=>{:c=>42}}}
Personally, I tend to prefer Ruby's enumerators if there's a natural way to express things there before looking at recursion.

Related

Add value to the front of hash and mutate

i want to add a value to the front of a hash and i want the hash to mutate.
Like so:
def put!(q,v)
q = {:value => v, :next => q}
end
But this doesn't work because i can't assign q a new value like this.
How would i do this?
Thanks in advance for all the answers.
I'm assuming that the :next key should have the image of the hash before modification. You can do this...
def put!(q,v)
q[:value], q[:next] = v, q.dup
end
the hash refrenced as q will be mutated so other references to the hash will reflect the change.
I have no idea why you'd ever want to do this, but here's a way to prepend a key & value pair to your hash :
hash = {b: 2, c: 3}
hash_copy = hash.dup
hash.clear
hash[:a] = 1
hash_copy.each do |k,v|
hash[k] = v
end
p hash
# {:a=>1, :b=>2, :c=>3}
It's slow and kinda useless: you need to duplicate your hash, remove all the keys, add one pair, and put all the pairs back.
The usual way would be to not care about the order :
hash = {b: 2, c: 3}
hash[:a] = 1
p hash
# {:b=>2, :c=>3, :a=>1}

ruby syntax code involving hashes

I was looking at code regarding how to return a mode from an array and I ran into this code:
def mode(array)
answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}
answer.select { |k,v| v == answer.values.max}.keys
end
I'm trying to conceptualize what the syntax means behind it as I am fairly new to Ruby and don't exactly understand how hashes are being used here. Any help would be greatly appreciated.
Line by line:
answer = array.inject ({}) { |k, v| k[v]=array.count(v);k}
This assembles a hash of counts. I would not have called the variable answer because it is not the answer, it is an intermediary step. The inject() method (also known as reduce()) allows you to iterate over a collection, keeping an accumulator (e.g. a running total or in this case a hash collecting counts). It needs a starting value of {} so that the hash exists when attempting to store a value. Given the array [1,2,2,2,3,4,5,6,6] the counts would look like this: {1=>1, 2=>3, 3=>1, 4=>1, 5=>1, 6=>2}.
answer.select { |k,v| v == answer.values.max}.keys
This selects all elements in the above hash whose value is equal to the maximum value, in other words the highest. Then it identifies the keys associated with the maximum values. Note that it will list multiple values if they share the maximum value.
An alternative:
If you didn't care about returning multiple, you could use group_by as follows:
array.group_by{|x|x}.values.max_by(&:size).first
or, in Ruby 2.2+:
array.group_by{&:itself}.values.max_by(&:size).first
The inject method acts like an accumulator. Here is a simpler example:
sum = [1,2,3].inject(0) { |current_tally, new_value| current_tally + new_value }
The 0 is the starting point.
So after the first line, we have a hash that maps each number to the number of times it appears.
The mode calls for the most frequent element, and that is what the next line does: selects only those who are equal to the maximum.
I believe your question has been answered, and #Mark mentioned different ways to do the calculations. I would like to just focus on other ways to improve the first line of code:
answer = array.inject ({}) { |k, v| k[v] = array.count(v); k }
First, let's create some data:
array = [1,2,1,4,3,2,1]
Use each_with_object instead of inject
My suspicion is that the code might be fairly old, as Enumerable#each_with_object, which was introduced in v. 1.9, is arguably a better choice here than Enumerable#inject (aka reduce). If we were to use each_with_object, the first line would be:
answer = array.each_with_object ({}) { |v,k| k[v] = array.count(v) }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
each_with_object returns the object, a hash held by the block variable v.
As you see, each_with_object is very similar to inject, the only differences being:
it is not necessary to return v from the block to each_with_object, as it is with inject (the reason for that annoying ; v at the end of inject's block);
the block variable for the object (k) follows v with each_with_object, whereas it proceeds v with inject; and
when not given a block, each_with_object returns an enumerator, meaning it can be chained to other other methods (e.g., arr.each_with_object.with_index ....
Don't get me wrong, inject remains an extremely powerful method, and in many situations it has no peer.
Two more improvements
In addition to replacing inject with each_with_object, let me make two other changes:
answer = array.uniq.each_with_object ({}) { |k,h| h[k] = array.count(k) }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
In the original expression, the object returned by inject (sometimes called the "memo") was represented by the block variable k, which I am using to represent a hash key ("k" for "key"). Simlarly, as the object is a hash, I chose to use h for its block variable. Like many others, I prefer to keep the block variables short and use names that indicate object type (e.g., a for array, h for hash, s for string, sym for symbol, and so on).
Now suppose:
array = [1,1]
then inject would pass the first 1 into the block and then compute k[1] = array.count(1) #=> 2, so the hash k returned to inject would be {1=>2}. It would then pass the second 1 into the block, again compute k[1] = array.count(1) #=> 2, overwriting 1=>1 in k with 1=>1; that is, not changing it at all. Doesn't it make more sense to just do this for the unique values of array? That's why I have: array.uniq....
Even better: use a counting hash
This is still quite inefficient--all those counts. Here's a way that reads better and is probably more efficient:
array.each_with_object(Hash.new(0)) { |k,h| h[k] += 1 }
#=> {1=>3, 2=>2, 4=>1, 3=>1}
Let's have a look at this in gory detail. Firstly, the docs for Hash#new read, "If obj is specified [i.e., Hash.new(obj)], this single object will be used for all default values." This means that if:
h = Hash.new('cat')
and h does not have a key dog, then:
h['dog'] #=> 'cat'
Important: The last expression is often misunderstood. It merely returns the default value. str = "It does *not* add the key-value pair 'dog'=>'cat' to the hash." Let me repeat that: puts str.
Now let's see what's happening here:
enum = array.each_with_object(Hash.new(0))
#=> #<Enumerator: [1, 2, 1, 4, 3, 2, 1]:each_with_object({})>
We can see the contents of the enumerator by converting it to an array:
enum.to_a
#=> [[1, {}], [2, {}], [1, {}], [4, {}], [3, {}], [2, {}], [1, {}]]
These seven elements are passed into the block by the method each:
enum.each { |k,h| h[k] += 1 }
=> {1=>3, 2=>2, 4=>1, 3=>1}
Pretty cool, eh?
We can simulate this using Enumerator#next. The first value of enum ([1, {}]) is passed to the block and assigned to the block variables:
k,h = enum.next
#=> [1, {}]
k #=> 1
h #=> {}
and we compute:
h[k] += 1
#=> h[k] = h[k] + 1 (what '+=' means)
# = 0 + 1 = 1 (h[k] on the right equals the default value
# of 1 since `h` has no key `k`)
so now:
h #=> {1=>1}
Next, each passes the second value of enum into the block and similar calculations are performed:
k,h = enum.next
#=> [2, {1=>1}]
k #=> 2
h #=> {1=>1}
h[k] += 1
#=> 1
h #=> {1=>1, 2=>1}
Things are a little different when the third element of enum is passed in, because h now has a key 1:
k,h = enum.next
#=> [1, {1=>1, 2=>1}]
k #=> 1
h #=> {1=>1, 2=>1}
h[k] += 1
#=> h[k] = h[k] + 1
#=> h[1] = h[1] + 1
#=> h[1] = 1 + 1 => 2
h #=> {1=>1, 2=>1}
The remaining calculations are performed similarly.

How do I create a hash where the keys are values from an array Ruby

I have an array:
arr = [a, ab, abc]
I want to make a hash, using the values of the array as the keys:
newhash = [a[1], ab[1], abc[1]]
I have tried:
arr.each do |r|
newhash[r] == 1
end
to no avail.
How would I about accomplishing this in ruby?
If you are feeling like a one-liner, this will work as well
h = Hash[arr.collect { |v| [v, 1] } ]
collect is invoked once per element in the array, so it returns an array of 2-element arrays of key-value pairs.
Then this is fed to the hash constructor, which turns the array of pairs into a hash
You could also use the #reduce method from Enumerable (which is included into the Array class).
new_hash = arr.reduce({}) { |hsh, elem| hsh[elem] = 1; hsh }
And your new_hash looks like this in Ruby:
{"a": 1, "ab": 1, "abc": 1}
== is comparison. = is assigning. So just modify == into =. It works.
newhash = {}
arr.each do |r|
newhash[r] = 1
end
(I believe a, ab, abc are strings)
To learn more, this might help you. Array to Hash Ruby
You can do it like this:
ary = [[:foo, 1], [:bar, 2]]
Hash[ary] # => {:foo=>1, :bar=>2}
If you want to do it like you tried earlier, you want to initialize hash correctly:
ary = [:foo, :bar]
hash = {}
ary.each do |key|
hash[key] = 1
end # => {:foo=>1, :bar=>2}

Returning an array from a method and storing it in a single variable and many ones

I have such code:
def test1
["123", "456"]
end
a = test1
a, b = test1
p a # => "123"
p a + "-" + b # => "123-456"
The methods return an array. Why does a equal to its first element and not the entire array?
This is how multiple assignment work. If the right side element is an array and there are more than one argument on the left side of assignment, array is being splated. Note that if you do:
a = test1
a is assigned with the whole array.
There are many application of this feature, for example when iterating over a hash:
hash = {a: 1, b: 2}
hash.each do |pair|
p pair
end
Internally hash is an array of 2-element arrays. First element is a key, second a value. Hence the code above will result in:
[:a, 1]
[:b, 2]
On the beginning of each iteration, ruby makes an assignment like
pair = [:a, 1]
With multiple assignments, you can then iterate over a hash like:
hash = {a: 1, b: 2}
hash.each do |key, value|
puts key
puts value
end
Which will change this assignment to:
key, value = [:a, 1]

finding out which object has n instances in ruby

I have an array: x = [a, b, c, d, e, f, g, h] which can have objects from 1 to 9
Firstly, I have to count IF any of these objects is present 3 times. I don't want to write
if (x.count(1) == 3) or (x.count(2) == 3) ...etc...
is there a way to shorten this, like below?
x.count { |obj| obj } == 3
Secondly, if I know that an object has been found with 3 instances, how can I find out which one was it? (1 or 2 or 3.....)
x = [:a, :b, :b, :b, :c, :c, :c]
counted = Hash[
x.group_by do |e|
x.count(e)
end.map do |count, items|
[count, items.uniq]
end
]
p counted[3] #=> [:b, :c]
How does this work? Let's follow the steps. First, let's group the items by count:
grouped_by_count = x.group_by do |e|
x.count(e)
end
This produces a hash with the keys being the counts, and the values being the list of non-unique items having that count:
p grouped_by_count
#=> {1=>[:a], 3=>[:b, :b, :b, :c, :c, :c]}
We'd really rather have unique items, though, so let's do that transform:
grouped_by_count_unique = grouped_by_count.map do |count, items|
[count, items.uniq]
end
p grouped_by_count_unique
#=> [[1, [:a]], [3, [:b, :c]]]
That gives us an array of arrays, and not a hash. Fortunately, it's easy to turn an array of arrays into a hash:
counted = Hash[grouped_by_count_unique]
p counted
# => {1=>[:a], 3=>[:b, :c]}
Now just put the pieces together eliminating the temporaries and you get the answer at the top.

Resources