Subtract two hashes in Ruby - ruby

Can the hash class be modified so that given two hashes, a new hash containing only keys that are present in one hash but not the other can be created?
E.g.:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h2 = {"Cat" => 100, "Dog" => 5, "Bison" => 30}
h1.difference(h2) = {"Bird" => 2, "Snake" => 10}
Optionally, the difference method could include any key/value pairs such that the key is present in both hashes but the value differs between them.

h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h2 = {"Cat" => 999, "Dog" => 5, "Bison" => 30}
Case 1: keep all key/value pairs k=>v in h1 for which there is no key k in h2
This is one way:
h1.dup.delete_if { |k,_| h2.key?(k) }
#=> {"Bird"=>2, "Snake"=>10}
This is another:
class Array
alias :spaceship :<=>
def <=>(o)
first <=> o.first
end
end
(h1.to_a - h2.to_a).to_h
#=> {"Bird"=>2, "Snake"=>10}
class Array
alias :<=> :spaceship
remove_method(:spaceship)
end
Case 2: keep all key/value pairs in h1 that are not in h2
All you need for this is:
(h1.to_a - h2.to_a).to_h
#=> {"Cat"=>100, "Bird"=>2, "Snake"=>10}
Array#to_h was introduced in Ruby 2.0. For earlier versions, use Hash[].

Use the reject method:
class Hash
def difference(other)
reject do |k,v|
other.has_key? k
end
end
end
To only reject key/value pairs if the values are identical (as per mallanaga's suggestion via a comment on my original answer, which I have deleted):
class Hash
def difference(other)
reject do |k,v|
other.has_key?(k) && other[k] == v
end
end
end

You can do this:
h2.each_with_object(h1.dup){|(k, v), h| h.delete(k)}

try using hashdiff gem.
diff=HashDiff.diff(h1,h2)

For deep nesting you can add a bit of recursion, something like (untested)
class Hash
def -(h2)
raise ArgumentError unless h2.is_a?(Hash)
h1 = dup
h1.delete_if do |k, v|
if v.is_a?(Hash) && h2[k].is_a?(Hash)
h1[k] = v - h2[k]
h1[k].blank?
else
h2[k] == v
end
end
end
end
end

Related

How to make a custom proc in a class so I can do: params_hash.downcase_keys instead of downcase_keys(params_hash)?

I have a hash of key values and I want to downcase all of the Keys.
However I don't want to have to create an local variable, I would rather functionally do it.
NOT:
x = downcase_keys(params_hash)
BUT THIS:
params_hash.downcase_keys
How would do this in ruby?
I do not understand why you tagged this question as functional-programming it seems you are looking for a method to call on a Hash object.
Be aware that you may encounter problems doing so because duplicated keys are going to be overwritten.
h = {"a" => 1, "B" => 2}
# Singleton method on the object
def h.downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
# Method available on all Hash objects
class Hash
def downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
end
h = {"a" => 1, "B" => 2}
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
def downcase_keys(hash)
hash.downcase_keys
end
h = {"C" => 1, "B" => 2, "D" => 3}
downcase_keys(h)
p h # {"c"=>1, "b"=>2, "d"=>3}

how can I programmatically identify which keys have sub key-value-pairs in a JSON doc? [duplicate]

This question already has answers here:
Flattening nested hash to a single hash with Ruby/Rails
(6 answers)
Closed 8 years ago.
I fetch a JSON document and need to programmatically "flatten" the keys for another third-party service.
What this means is, if my JSON doc comes back with the following:
{'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
I need to be able to know to create a "flat" key-value pair for a third-party service like this:
first_name = "Joe"
hoffman.patterns = "negativity, self-sabotage"
hoffman.right_road = "happy family"
mbti = "INTJ"
Once I know there's a sub-document, the parsing I think I have figured out just appending the sub-keys with key + '.' + "{subkey}" but right now, don't know which ones are straight key-value and which one's have sub-documents.
Question:
a) How can I parse the JSON to know which keys have sub-documents (additional key-values)?
b) Suggestions on ways to create a string from an array
You could also monkey patch Hash to do this on it's own like so:
class Hash
def flatten_keys(prefix=nil)
each_pair.map do |k,v|
key = [prefix,k].compact.join(".")
v.is_a?(Hash) ? v.flatten_keys(key) : [key,v.is_a?(Array) ? v.join(", ") : v]
end.flatten.each_slice(2).to_a
end
def to_flat_hash
Hash[flatten_keys]
end
end
Then it would be
require 'json'
h = JSON.parse(YOUR_JSON_RESPONSE)
#=> {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Will work with additional nesting too
h = {"first_name"=>"Joe", "hoffman"=>{"patterns"=>["negativity", "self-sabotage"], "right_road"=>"happy family", "wrong_road"=>{"bad_choices"=>["alcohol", "heroin"]}}, "mbti"=>"INTJ"}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "hoffman.wrong_road.bad_choices"=>"alcohol, heroin", "mbti"=>"INTJ"}
Quick and dirty recursive proc:
# assuming you've already `JSON.parse` the incoming json into this hash:
a = {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
# define a recursive proc:
flatten_keys = -> (h, prefix = "") do
#flattened_keys ||= {}
h.each do |key, value|
# Here we check if there's "sub documents" by asking if the value is a Hash
# we also pass in the name of the current prefix and key and append a . to it
if value.is_a? Hash
flatten_keys.call value, "#{prefix}#{key}."
else
# if not we concatenate the key and the prefix and add it to the #flattened_keys hash
#flattened_keys["#{prefix}#{key}"] = value
end
end
#flattened_keys
end
flattened = flatten_keys.call a
# => "first_name"=>"Joe", "hoffman.patterns"=>["negativity", "self-sabotage"], "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
And then, to turn the arrays into strings just join them:
flattened.inject({}) do |hash, (key, value)|
value = value.join(', ') if value.is_a? Array
hash.merge! key => value
end
# => {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Another way, inspired by this post:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
result = {}
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
.each{ |k, v| result[k.join('.')] = v } #=> {"a.b.c"=>1, "a.b.d"=>2, "a.e"=>3, "f"=>4}

A hash-like object that acts like a case statement

What is the best way to construct a hash-like class Case, which is initialized by a hash:
cs = Case.new(:a => 1, /b/ => 2, /c/ => 2, /d/ => 3)
and has a method Case#[] that looks up for the first matching key by === (like a case statement) instead of by == (like the conventional hash) and returns the value:
cs["xxb"] => 2
Here's a possibility.
class Case
def initialize(h)
#h = h
end
def [](key,order=:PRE)
case order
when :PRE
h[#h.keys.find { |k| key === k }]
when :POST
h[#h.keys.find { |k| k === key }]
else
# raise exception
end
end
end
cs = Case.new(:a => 1, /b/ => 2, /c/ => 2, [1,2] => "cat", /d/ => 3)
cs["xxb"] #=> nil
cs["xxb",:POST] #=> 2
cs[Regexp] #=> 2
cs[Regexp,:POST] #=> nil
cs[Array] #=> "cat"
cs[Symbol] #=> 1
This assumes h does not have a key nil.
With the understanding that the key in the hash is to come on the left side of ===, the code would be:
class Case
def initialize(h) #h = h end
def [](key) h[#h.keys.find{|k| k === key}] end
end

Hash.each doesn't return a hash?

Hash.each returns an array [key, value],
but if I want a hash?
Example: {:key => value }
I'm assuming you meant "yields" where you said "return" because Hash#each already returns a hash (the receiver).
To answer your question: If you need a hash with the key and the value you can just create one. Like this:
hash.each do |key, value|
kv_hash = {key => value}
do_something_with(kv_hash)
end
There is no alternative each method that yields hashs, so the above is the best you can do.
I think you are trying to transform the hash somehow, so I will give you my solution to this problem, which may be not exactly the same. To modify a hash, you have to .map them and construct a new hash.
This is how I reverse key and values:
h = {:a => 'a', :b => 'b'}
Hash[h.map{ |k,v| [v, k] }]
# => {"a"=>:a, "b"=>:b}
Call .each with two parameters:
>> a = {1 => 2, 3 => 4}
>> a.each { |b, c|
?> puts "#{b} => #{c}"
>> }
1 => 2
3 => 4
=> {1=>2, 3=>4}
You could map the hash to a list of single-element hashes, then call each on the list:
h = {:a => 'a', :b => 'b'}
h.map{ |k,v| {k => v}}.each{ |x| puts x }

hash assignment when (key => value) are stored in an array? (ruby)

I have hash (#post) of hashes where I want to keep the order of the hash's keys in the array (#post_csv_order) and also want to keep the relationship key => value in the array.
I don't know the final number of both #post hashes and key => value elements in the array.
I don't know how to assign the hash in a loop for all elements in the array. One by one #post_csv_order[0][0] => #post_csv_order[0][1] works nicely.
# require 'rubygems'
require 'pp'
#post = {}
forum_id = 123 #only sample values.... to make this sample script work
post_title = "Test post"
#post_csv_order = [
["ForumID" , forum_id],
["Post title", post_title]
]
if #post[forum_id] == nil
#post[forum_id] = {
#post_csv_order[0][0] => #post_csv_order[0][1],
#post_csv_order[1][0] => #post_csv_order[1][1]
##post_csv_order.map {|element| element[0] => element[1]}
##post_csv_order.each_index {|index| #post_csv_order[index][0] => #post_csv_order[index][1] }
}
end
pp #post
desired hash assignment should be like that
{123=>{"Post title"=>"Test post", "ForumID"=>123}}
The best way is to use to_h:
[ [:foo,1],[:bar,2],[:baz,3] ].to_h #=> {:foo => 1, :bar => 2, :baz => 3}
Note: This was introduced in Ruby 2.1.0. For older Ruby, you can use my backports gem and require 'backports/2.1.0/array/to_h', or else use Hash[]:
array = [[:foo,1],[:bar,2],[:baz,3]]
# then
Hash[ array ] #= > {:foo => 1, :bar => 2, :baz => 3}
This is available in Ruby 1.8.7 and later. If you are still using Ruby 1.8.6 you could require "backports/1.8.7/hash/constructor", but you might as well use the to_h backport.
I am not sure I fully understand your question but I guess you want to convert a 2d array in a hash.
So suppose you have an array such as:
array = [[:foo,1],[:bar,2],[:baz,3]]
You can build an hash with:
hash = array.inject({}) {|h,e| h[e[0]] = e[1]; h}
# => {:foo=>1, :bar=>2, :baz=>3}
And you can retrieve the keys in correct order with:
keys = array.inject([]) {|a,e| a << e[0] }
=> [:foo, :bar, :baz]
Is it what you were looking for ?
Answers summary
working code #1
#post[forum_id] = #post_csv_order.inject({}) {|h,e| h[e[0]] = e[1]; h}
working code #2
#post[forum_id] = Hash[*#post_csv_order.flatten]
working code #3
#post[forum_id] ||= Hash[ #post_csv_order ] #requires 'require "backports"'

Resources