How to use object as a dictionary key in YAML? - yaml

I have the following simple class:
class A:
def __init__(self, a=None, b=None):
self.a = a
self.b = b
It's in Python but it shouldn't matter. I have a map/dictionary that uses instances of this class as keys. E.g.:
m = {
A(a=1): 2,
A(a=3, b=4): 5,
A(b=6): 7
}
How can I configure this data in YAML? I've tried
{ a: 1 } : 2
{ a: 3, b: 4 } : 5
{ b: 6 } : 7
but I'm getting 'duplicated mapping key' error on the second line. I understand it's possible to write
[1, null] : 2
[3, 4] : 5
[null, 6] : 7
but I would like to keep named fields.
Alternatively, is there another configuration format that would let me do that?

I've been able to solve this issue by using explicit constructor
def yaml_a_constructor(loader, node):
fields = loader.construct_mapping(node)
return A(fields.get('a'), fields.get('b'))
yaml.add_constructor('!A', yaml_a_constructor)
and YAML
!A { a: 1 } : 2
!A { a: 3, b: 4 } : 5
!A { b: 6 } : 7
.

Related

easier and shorter way to sum values in a hash of hashes in ruby

Having a hash of hashes like this:
d = {a: {c: 1, d:3 }, b: {c: 2, d: 6}, ...}
What is the easier way to sum all values in c:?
d.values.map { |val| val[:c] }.reduce(&:+)
To explain:
d.values
=> [{:c=>1, :d=>3}, {:c=>2, :d=>6}]
d.values.map { |val| val[:c] }
=> [1, 2]
From this point you can use reduce(&:+) to get the sum, or if you're using rails (or have required active support), you can use Array#sum
reduce(&:+), by the way, is a shorthand for reduce { |memo, val| memo + val }
Just go over the hashes and sum their :c values.
d.values.sum { |h| h[:c] }
=> 3
Even shorter (from Sagar Pandya's comment):
d.sum { |_, v| v[:c] }
=> 3
If you wish to permit nested hashes of arbitrary depth you can use the following recursive method.
def sum_cees(h)
h.sum { |k,v| v.is_a?(Hash) ? sum_cees(v) : k == :c ? v : 0 }
end
sum_cees({ a: { c: 1, d:3 }, b: { d: { m: { c: 2, e: 6 } }, f: { c: 3} },
g: { c: 4 }, n: { r: 3 } })
#=> 10

What's the difference `{'x'=> 3}` and `{x: 3}`?

I have this:
a = {'x' => 3}
b = {'x': 3}
c = {x: 3}
d = {:x => 3}
e = {:'x' => 3}
So, I have that b = c = d = e = {:x => 3}, meanwhile a = {"x" => 3} but a.class == b.class.
I don't understand what the difference is between a and the rest of variables.
In b,c,d, and e, the key is a Symbol.
In a, the key is a String.
a = { 'x' => 3 } #=> { "x" => 3 }
b = { 'x': 3 } #=> { :x => 3 }
c = { x: 3 } #=> { :x => 3 }
d = { :x => 3 } #=> { :x => 3 }
e = { :'x' => 3 } #=> { :x => 3 }
Your variable a hash has "x" key as a string, while other variables have that key as symbol.
Calling class on an object in Ruby returns its class, in your example it is Hash. In other words, the constructor of all hash instances, such as {x: 3} is Hash object.
There is a significant difference between String and Symbol classes in ruby:
explanation on SO;
good article on the topic.
By convention, all but the very first hash notations cast keys to the Symbol instance, while the first one uses the key (String instance in this particular case) as is. (To be more precise: b and c cast key to the Symbol instance, d and e do not cast anything, but keys given in these cases are Symbol instances already.)
Since ('x' == :x) == false, a hash differs from the latters.

Determine if a hash is a nested hash in Ruby

Is there a way to determine if a hash is a nested hash in ruby ?
For example
a = { a: 1, b: 2, c: 2 }
should return false
a = { a: {a1: 1, a2: 2}, b: {b1: 1}, c: 2 }
should return true
a.any? { |_, v| v.is_a?(Hash) }
You can check it by iterating over your hash values with Hash#values method:
a.values.any? { |v| v.is_a?(Hash) }

Can I do the union operator for two hashes and keep the max value

I wonder is there efficient way to get my goal in Ruby.
Keep all the keys in the two hashed, and keep the larger value.
input
h1 = {
a: 0,
b: 1,
c: 2
}
h2 = {
a: 7,
c: 9
}
output
{
a: 7,
b: 1,
c: 9
}
Hash#merge can take a block:
h1.merge(h2) {|key, old, new| old > new ? old : new}
# => {:a=>7, :b=>1, :c=>9}
Hash#merge can take block. You can use it to get max value:
h1.merge(h2) { |key, v1, v2| [v1, v2].max }
# => {:a=>7, :b=>1, :c=>9}

Custom comparison operator for difference between arrays in Ruby

I have two arrays of some class. I want to find the difference between them. But I want it to be based on the value of a specific method call to each instance of this class, instead of the entire instance. Here is some example code where I use Hash for the class.
#!/usr/bin/ruby
require 'awesome_print'
class Hash
def <=> other
puts 'space ship'
self[:a] <=> other[:a]
end
def == other
puts 'double equal'
self[:a] == other[:a]
end
def > other
puts 'greater than'
self[:a] > other[:a]
end
def < other
puts 'less than'
self[:a] < other[:a]
end
def >= other
puts 'greater equal'
self[:a] >= other[:a]
end
def <= other
puts 'less equal'
self[:a] <= other[:a]
end
def eql? other
puts 'eql?'
self[:a].eql? other[:a]
end
def equal? other
puts 'equal?'
self[:a].equal? other[:a]
end
end
c = { a: 1, b: 2, c: 3}
d = { a: 2, b: 3, c: 4}
e1 = { a: 3, b: 4, c: 5}
e2 = { a: 3, b: 4, c: 5}
e3 = { a: 3, b: 5, c: 4}
f1 = { a: 4, b: 5, c: 6}
f2 = { a: 4, b: 5, c: 6}
f3 = { a: 4, b: 6, c: 5}
g = { a: 5, b: 6, c: 7}
h = { a: 6, b: 7, c: 8}
a = [c, d, e1, f1]
b = [e3, f3, g, h]
ap (a - b)
I expect to see 2 elements in the final array, but still seeing 4. Tried overriding all the various comparison operators for the class of each element, Hash in this case, and I can see some calls to the 'double equal', but it still doesn't have the proper effect. What am I doing wrong?
Array#- uses the eql? / hash protocol, just like Hash, Set and Array#uniq:
class Hash
def eql? other
puts 'eql?'
self[:a].eql? other[:a]
end
def hash
puts 'hash'
self[:a].hash
end
end
c = { a: 1, b: 2, c: 3}
d = { a: 2, b: 3, c: 4}
e1 = { a: 3, b: 4, c: 5}
e2 = { a: 3, b: 4, c: 5}
e3 = { a: 3, b: 5, c: 4}
f1 = { a: 4, b: 5, c: 6}
f2 = { a: 4, b: 5, c: 6}
f3 = { a: 4, b: 6, c: 5}
g = { a: 5, b: 6, c: 7}
h = { a: 6, b: 7, c: 8}
a = [c, d, e1, f1]
b = [e3, f3, g, h]
a - b
# => [{a: 1, b: 2, c: 3}, {a: 2, b: 3, c: 4}]

Resources