Sort hash by array - ruby

I have a hash and an array with same length like the following:
h = {:a => 1, :b => 2, :c => 3, :d => 4}
a = [2, 0, 1, 0]
I want to order the hash in increasing order of the values in the array. So the output would be something like:
h = {:b => 2, :d => 4, :c=> 3, :a => 1}
Ideally I want to introduce some randomness for ties. For the previous example, I want either the previous output or:
h = {:d => 4, :b => 2, :c=> 3, :a => 1}
This is what I tried.
b = a.zip(h).sort.map(&:last)
p Hash[b]
# => {:b=>2, :d=>4, :c=>3, :a=>1}
But I am not sure how to introduce the randomness.

h.to_a.sort_by.each_with_index{|el,i| [a[i], rand]}.to_h

You could modify what you have slightly:
def doit(h,a)
Hash[a.zip(h).sort_by { |e,_| [e,rand] }.map(&:last)]
end
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { d=>4, b=>2, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }

Related

Ruby - Hash to methods recursively

I have a hash-of-hashes and I need to turn it into methods where they key is the method name, and the value is what the method returns, but with the extra requirement that if a value is a also a hash, the whole value is returned.
For instance:
hash = {:a => 1, :b => 2, :c => { :a => 1, :b => 2, :c => 3} }
hash.c.a #=> 1
hash.c #=> { :a => 1, :b => 2, :c => 3}
How would I be able to do this? Help appreciated.
You could use OpenStruct to make a data structure like that. For example:
require 'ostruct'
s = OpenStruct.new(a: 1, b: 2, c: OpenStruct.new(a: 1, b: 2, c: 3))
s.c.a # => 1
s.c # => #<OpenStruct a=1, b=2, c=3>
Another option is to use Hashie. Hashie is a set of hash-like extensions. Among those are hashes that you can access directly like fields - see specifically the Mash option:
mash = Hashie::Mash.new(:a => 1, :b => 2, :c => Hashie::Mash.new(:a => 1, :b => 2, :c => 3))
As your receiver is a hash, you must define the methods on the class Hash:
def makem(h)
h.each do |k,v|
case v
when Hash
Hash.instance_eval { define_method(k.to_s) { v } }
v.each { |kk,vv| Hash.instance_eval { define_method(kk.to_s) { vv } } }
end
end
end
Let's try it:
h = {:a => 1, :b => 2, :c => { :a => 1, :b => 2, :d => 3 } }
makem(h)
Hash.instance_methods(false).select { |m| m.size == 1 }
#=> [:c, :a, :b, :d]
h.c
#=> {:a=>1, :b=>2, :d=>3}
h.c.a
#=> 1
h.c.b
#=> 2
h.c.d
#=> 3
In your example, you have:
:c => { :a => 1, :b => 2, :c => 3}
If you want a method c that returns the above hash, you obviously cannot define another method of the same name that would return 3.
You may wish to reconsider the advisability of defining these methods. Perhaps it would be more useful to construct methods such as def m(hash, key) within your class.

Ruby: Sum selected hash values

I've got an array of hashes and would like to sum up selected values. I know how to sum all of them or one of them but not how to select more than one key.
i.e.:
[{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
To sum all of them I using:
t = h.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
=> {"a"=>15, "b"=>30, "active"=>"yesnoyes"} # I do not want 'active'
To sum one key, I do:
h.map{|x| x['a']}.reduce(:+)
=> 15
How do I go about summing up values for keys 'a' and 'b'?
You can use values_at:
hs = [{:a => 1, :b => 2, :c => ""}, {:a => 2, :b => 4, :c => ""}]
keys = [:a, :b]
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map { |xy| xy.compact.sum }}
# => [3, 6]
If all required keys have values it will be shorter:
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map(&:sum) }
# => [3, 6]
If you want Hash back:
Hash[keys.zip(hs.map { |h| h.values_at(*keys) }.inject{ |a, v| a.zip(v).map(&:sum) })]
# => {:a => 3, :b => 6}
I'd do something like this:
a.map { |h| h.values_at("a", "b") }.transpose.map { |v| v.inject(:+) }
#=> [15, 30]
Step by step:
a.map { |h| h.values_at("a", "b") } #=> [[5, 10], [5, 10], [5, 10]]
.transpose #=> [[5, 5, 5], [10, 10, 10]]
.map { |v| v.inject(:+) } #=> [15, 30]
How is this ?
h = [{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
p h.map{|e| e.reject{|k,v| %w(active action).include? k } }.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
# >> {"a"=>15, "b"=>30}

Ruby creating Hash custom invert function in Ruby

Ruby class Hash has method "invert" which make "reversal" between keys and values and delete same keys (in our case its: "1=>:a").
h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
h.invert
=> {1=>:c, 2=>:b}
How implement custom Hash method "c_invert", which will return very first (not last) pair of duplicated key => value? Exapmle:
> h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
> h.c_invert
=> {1=>:a, 2=>:b}
class Hash
def c_invert
Hash[to_a.reverse].invert
end
end
or
class Hash
def c_invert
Hash[to_a.reverse.map(&:reverse)]
end
end
h = {:d =>1,:a=>1, :b=> 2, :c=>1}
Hash[h.map(&:reverse).reverse]
# => {1=>:d, 2=>:b}
h = {a: 1, b: 2, c: 1}
Hash[h.map(&:reverse).reverse]
# => {1=>:a, 2=>:b}

How do I map an array of hashes?

I have an array of hashes:
arr = [ {:a => 1, :b => 2}, {:a => 3, :b => 4} ]
What I want to achieve is:
arr.map{|x| x[:a]}.reduce(:+)
but I think it's a bit ugly, or at least not that elegant as:
arr.map(&:a).reduce(:+)
The later one is wrong because there is no method called a in the hashes.
Are there any better ways to write map{|x| x[:a]}?
You could make actual Objects, possibly with a Struct:
MyClass = Struct.new :a, :b
arr = [MyClass.new(1, 2), MyClass.new(3, 4)]
arr.map(&:a).reduce(:+) #=> 4
Or for more flexibility, an OpenStruct:
require 'ostruct'
arr = [OpenStruct.new(a: 1, b: 2), OpenStruct.new(a: 3, b: 4)]
arr.map(&:a).reduce(:+) #=> 4
Of course either of these can be constructed from existing hashes:
arr = [{ :a => 1, :b => 2 }, { :a => 3, :b => 4 }]
ss = arr.map { |h| h.values_at :a, :b }.map { |attrs| MyClass.new(*attrs) }
ss.map(&:a).reduce(:+) #=> 4
oss = arr.map { |attrs| OpenStruct.new attrs }
oss.map(&:a).reduce(:+) #=> 4
Or, for a more creative, functional approach:
def hash_accessor attr; ->(hash) { hash[attr] }; end
arr = [{ :a => 1, :b => 2 }, { :a => 3, :b => 4 }]
arr.map(&hash_accessor(:a)).reduce(:+) #=> 4
It is unclear what you mean as "better" and why you think the correct version is ugly.
Do you like this "better"?
arr.inject(0) { |sum, h| sum + h[:a] }
There's a way, extending the Symbol.
lib/core_extensions/symbol.rb (credit goes here)
# frozen_string_literal: true
class Symbol
def with(*args, &)
->(caller, *rest) { caller.send(self, *rest, *args, &) }
end
end
Then, given:
arr = [ {:a => 1, :b => 2}, {:a => 3, :b => 4} ]
you can do this:
arr.map(&:[].with(:a)).reduce(:+)
Explanation: to access hash value under any key, you call Hash#[] method. When passed as a :[] (extended) symbol to the Array#map, you can then call .with(*args) on this symbol, effectively passing the parameter (hash key) down to the :[] method. Enjoy.

Finding duplicates in nested arrays

I have a hash, which contains a hash, which contains a number of arrays, like this:
{ "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [8, 10, 12]
}
}
I would like to compare the arrays against the other arrays, and then alert me if they are duplicates. It is possible for hash["bob"]["foo"] and hash["fred"]["foo"] to have duplicates, but not for hash["bob"]["foo"] and hash["bob"]["bar"]. Same with hash["fred"].
I can't even figure out where to begin with this one. I suspect inject will be involved somewhere, but I could be wrong.
This snippet will return an array of duplicates for each key. Duplicates can only be generated for equal keys.
duplicates = (keys = h.values.map(&:keys).flatten.uniq).map do |key|
{key => h.values.map { |h| h[key] }.inject(&:&)}
end
This will return [{"foo"=>[1]}, {"bar"=>[]}] which indicates that the key foo was the only one containing a duplicate of 1.
The snippet above assume h is the variable name of your hash.
h = {
"bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [1, 10, 12]
}
}
h.each do |k, v|
numbers = v.values.flatten
puts k if numbers.length > numbers.uniq.length
end
There are many ways to do it.
Here's one that should be easy to read.
It works in Ruby 1.9. It uses + to combine two arrays and then uses the uniq! operator to figure out whether there is a duplicate number.
h = { "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 12],
"bar" => [8, 10, 12]
}
}
h.each do |person|
if (person[1]["foo"] + person[1]["bar"]).uniq! != nil
puts "Duplicate in #{person[1]}"
end
end
I'm not sure what exactly you are looking for. But at look at a possible solution, perhaps you can reuse something.
outer_hash.each do |person, inner_hash|
seen_arrays = Hash.new
inner_hash.each do |inner_key, array|
other = seen_arrays[array]
if other
raise "array #{person}/#{inner_key} is a duplicate of #{other}"
end
seen_arrays[array] = "#{person}/#{inner_key}"
end
end

Resources