I have an array of Hashes:
[{"folder"=>"j"}, {"path"=>"p"}, {"folder"=>"b"}]
I merge them via:
flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.map(&:last)]}
Then I get:
[{"folder"=>["j", "b"]}, {"path"=>["p"]}]
as expected.
My question is, :folder and :path (I will also have more keys) can be randomly ordered. Instead of looping and checking for key names, Is it possible to get each hash separately? For example;
# After merging...
folders = elem[:folder] # => ["j", "b"]
paths = elem[:path].. # => ["p"]
Basically I would like to get :folder and :path without looping all the time.
You could use a different approach in the first place:
collection = [{"folder"=>"j"}, {"path"=>"p"}, {"folder"=>"b"}]
Using each_with_object (or a similar approach with reduce):
collection.each_with_object({}) do |values, s|
values.each do |key, value|
(s[key.to_sym] ||= []) << value
end
end
Demonstration
or group_by and transform_values:
collection.
group_by { |item| item.first.first.to_sym }.
transform_values { |values| values.flat_map(&:values) }
You can solve your problem by this:
elem = [{"folder"=>["j", "b"]}, {"path"=>["p"]}]
merged = elem.reduce({}, :merge)
# => {"folder" => ["j", "b"], "path" => ["p"]}
merged["folder"]
# => ["j", "b"]
merged["path"]
# => ["p"]
ar =[{"folder"=>"j"}, {"path"=>"p"}, {"folder"=>"b"}]
elem = Hash.new{[]}
ar.each{|hash| hash.each{|k,v| elem[k] <<= v }}
folders, paths = elem.values_at("folder", "path")
Related
I have a JSON data structure like this...
{
"items": [
{
"person": { // person hash }
},
{
"dog": { // dog hash }
},
{
"fruit": { // fruit hash }
},
{
“person”: { // person hash }
}
]
}
}
Each item in the array contains only one key:value pair. The key is the bot that tells me what type of item the value is.
What I'd like to do is iterate the array and run a different function for each type of item.
So I have something like this...
items = data.dig('items')
items.map do |item|
if person = item.dig('person')
transform_person(person)
elsif dog = item.dig('dog')
transform_dog(dog)
elsif fruit = item.dig('fruit')
transform_fruit(fruit)
end
end
But I feel like there should be a more elegant way to do this?
Apologies. I appear to have left some ambiguity in my question.
The initial array may contain multiple items with the same key. What I am trying to do is map to an array of items that are transformed into what is required by the front end. The input contains a strange structure and info that is not needed by the front end.
So the output array order must match the input array order.
Sorry for the confusion.
First you'll want to define the key preference in a constant:
PECKING_ORDER = %w[ person dog fruit ]
Then you can use that to find it:
def detect(item)
PECKING_ORDER.lazy.map do |key|
[ key, item.dig(key) ]
end.find do |key, v|
v
end
end
Where that can dig up the first item that's found. lazy is used here so it doesn't dig them all up needlessly, just does them one at a time until there's a hit.
This gives you a key/value pair which you can use with dynamic dispatch:
items.each do |item|
key, value = detect(item)
if (key)
send(:"transform_#{key}", value)
end
end
if you know the mapping, you could make a pseudo factory hash:
methods_mapped = {
"person" => ->(person) { do_something_with_person(person) },
"dog" => ->(dog) { do_something_with_dog(dog) },
"fruit" => ->(fruit) { do_something_with_fruit(fruit) }
}
items.map do |item|
key = item.keys.first # what if keys.size > 1 ?
method = methods_mapped.fetch(key)
method.call(item[key])
end
or you could it from the opposite direction:
methods_mapped.each do |key, method|
method.call(items.dig(key))
end
Let f be a given method that takes as an argument a hash. Without loss of generality, suppose it is as follows. This corresponds to the OP's transform_person, transform_dog and transform_fruit methods combined.
def f(h)
case h.keys.first
when :person then "somebody"
when :dog then "doggie"
when :fruit then "juicy"
end
end
Suppose we are also given (no need for dig here)
items = data[:items]
#=> [{:person=>{:name=>"Melba"}},
# {:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# {:fruit=>{:good=>"raspberries"}}]
and
key_order = [:bird, :marsupial, :dog, :person]
We wish to find the first element k of key_order for which items contains a hash h for which h.key?(k) #=> true. If such a hash h is found we are to then execute f(h).
First compute a hash key_map.
key_map = items.each_with_object({}) { |g,h| h[g.keys.first] = g }
#=> {:person=>{:person=>{:name=>"Melba"}},
# :dog=>{:dog=>{:tricks=>[:roll_over, :shake_a_paw]}},
# :fruit=>{:fruit=>{:good=>"raspberries"}}}
Then we simply execute
k = key_order.find { |k| key_map[k] }
#=> :dog
k ? f(key_map[k]) : nil
#=> "doggie"
I would kept it simple:
items.map do |item|
do_something_with_person(item) if item.dig('person')
do_something_with_dog(item) if item.dig('dog')
do_something_with_fruit(item) if item.dig('fruit')
end
or
items.each do |item|
case item
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
or
def do_something(item)
case
when item.dig('person') then do_something_with_person(item)
when item.dig('dog') then do_something_with_dog(item)
when item.dig('fruit') then do_something_with_fruit(item)
end
end
items.map { |item| do_something(item) }
I have an array of hashes like this:
items = [{"id"=>"123", "code"=>"abc","name"=>"test", "type"=>["good"]},
{"id"=>"555", "code"=>"ddd","name"=>"foo", "type"=>["better"]},
{"id"=>"098", "code"=>"zyx","name"=>"bar", "type"=>["best"]}]
I am trying to sort each hash within the array by the key.
I tried this:
items.each { |item| item = item.sort.to_h }
It returns the same result:
[{"id"=>"123", "code"=>"abc", "name"=>"test", "type"=>["good"]},
{"id"=>"555", "code"=>"ddd", "name"=>"foo", "type"=>["better"]},
{"id"=>"098", "code"=>"zyx", "name"=>"bar", "type"=>["best"]}]
but when I try this:
items[0].sort.to_h
this is the result:
{"code"=>"abc", "id"=>"123", "name"=>"test", "type"=>["good"]}
So it looks like when I call the individual elements within items using items[x] where x is an index value within the array, it sort it.
But I need a solution to iterate through each element doing that and saving the sort.
Any thoughts?
I solved it with this:
items.map { |item| item.sort.to_h }
Thanks #SagarPandya and #Dark
If, as in the example, all the hashes have the same keys, it would be faster to perform a single sort on the keys.
sorted_keys = items.first.keys.sort
#=> ["code", "id", "name", "type"]
items.map { |item| sorted_keys.each_with_object({}) { |k,h| h[k] = item[k] } }
#=> [{"code"=>"abc", "id"=>"123", "name"=>"test", "type"=>["good"]},
# {"code"=>"ddd", "id"=>"555", "name"=>"foo", "type"=>["better"]},
# {"code"=>"zyx", "id"=>"098", "name"=>"bar", "type"=>["best"]}]
The last line could alternatively be written
items.map { |item| sorted_keys.zip(item.values_at(*sorted_keys)).to_h }
With Ruby (2.4), I want to extend the core hash functionality to search for keys based on an array and return the value from the first element that can be found from that array. I have this in my lib/core_ext/hash_with_indifferent_access.rb file ...
class CaseInsensitiveHash < HashWithIndifferentAccess
# This method shouldn't need an override, but my tests say otherwise.
def [](key)
if key.kind_of?(Array)
find_by_arr(arr)
else
super convert_key(key)
end
end
protected
def convert_key(key)
key.respond_to?(:downcase) ? key.downcase : key
end
def find_by_arr(arr)
arr.inject(self){|h, k| h.to_h[k]}
end
end
However, it is not working as expected. In my code below, the search 'h[["a", "b"]]' should produce "1", because the first element, "a", is a key in my hash.
2.4.0 :001 > h = {"a" => 1, "b" => 2}
=> {"a"=>1, "b"=>2}
2.4.0 :002 > h["a"]
=> 1
2.4.0 :003 > h[["a", "b"]]
=> nil
How do I modify my code so that I can pass in an array as a key to a hash and it will start searching for values iteratively by each element in the array?
You've almost got it, but the problem is that h = { ... } creates a plain-old Hash, not the kind you've added these methods to.
The first fix is to do this:
h = CaseInsensitiveHash["a" => 1, "b" => 2]
Then you get the right type of object and your method actually runs.
There's a typo in your [] method which can be corrected:
def [](key)
case key
when Array
find_by_arr(key)
else
super convert_key(key)
end
end
Your reference to arr doesn't work because that's not defined.
The find_by_arr method also returns the last match, not the first. This can be fixed:
def find_by_arr(arr)
self[arr.first { |key| self[key] }]
end
Say I have such an array:
arr = ['footballs_jumba_10', 'footballs_jumba_11', 'footballs_jumba_12',
'footballs_jumba_14', 'alpha_romeo_11', 'alpha_romeo_12',
'alpha_juliet_10', 'alpha_juliet_11']
If I wanted to return duplicates, (assuming any of these strings in the array were exactly identical, I would just
return arr.detect{ |a| arr.count(a) > 1 }
but, what if I wanted to get only duplicates of the first 10 characters of each element of the array, without knowing the variations beforehand? Like this:
['footballs_', 'alpha_rome', 'alpha_juli']
This is quite straightforward with the method Arry#difference that I proposed in my answer here:
arr << "Let's add a string that appears just once"
#=> ["footballs_jumba_10", "footballs_jumba_11", "footballs_jumba_12",
# "footballs_jumba_14", "alpha_romeo_11", "alpha_romeo_12",
# "alpha_juliet_10", "alpha_juliet_11", "Let's add a string that appears just once"]
a = arr.map { |s| s[0,10] }
#=> ["footballs_", "footballs_", "footballs_", "footballs_", "alpha_rome",
# "alpha_rome", "alpha_juli", "alpha_juli", "Let's add "]
b = a.difference(a.uniq)
#=> ["footballs_", "footballs_", "footballs_", "alpha_rome", "alpha_juli"]
b.uniq
#=> ["footballs_", "alpha_rome", "alpha_juli"]
Use Array#uniq:
arr.map {|e| e[0..9]}.uniq
# => ["footballs_", "alpha_rome", "alpha_juli"]
You could do something like this:
def partial_duplicates(elements)
unique = {}
duplicates = {}
elements.each do |e|
partial = e[0..9]
# If the element is in the hash, it is a duplicate.
if first_element = unique[partial]
duplicates[first_element] = true
duplicates[e] = true
else
# include the element as unique
unique[partial] = e
end
end
duplicates.keys
end
This will return unique duplicates. If you want all the duplicates, you can just use an Array.
Also, this returns all the full representations of each duplicate as it seems more useful and probably what you want:
partial_duplicates(arr)
=> ["footballs_jumba_10", "footballs_jumba_11", "footballs_jumba_12", "footballs_jumba_14", "alpha_romeo_11", "alpha_romeo_12", "alpha_juliet_10", "alpha_juliet_11"]
If you want only the partial duplicates you can change the condition to:
if unique[partial]
duplicates[partial] = true
else
unique[partial] = true
end
then:
partial_duplicates(arr)
=> ["footballs_", "alpha_rome", "alpha_juli"]
How to do a Perl program that contains an array and that array points a hash?
It is like this pictorially,
(M1) (M2) ...it goes on
|--k1=>v1 |--K1=>v1
|--k2=>v2 |--k2=>v2
I should access that array M1, then the hash it contains inside. (and so on)...
This should do it - though it isn't quite clear to me how you wanted 'M1' and 'M2' to play into the scenario:
my(#array) = ( { k1 => "v1", k2 => "v2" }, { K1 => "V1", K2 => "V2" } );
print "$array[0]->{k1}\n";
print "$array[1]->{K2}\n";
You are making your life more interesting when you use different sets of keys in the different elements of the array (k1 and k2 versus K1 and K2). That's far from forbidden, but it makes the processing harder.
You need to use hash references:
my #array;
push #array, { k1=>"v1", k2=>"v2" }, { k1=>"v1", k2=>"v2" };
Then, access the hashes like this:
my $val = $array[0]{k1};
Something like:
%h1 = ('a'=>'abc','b'=>'bcd'); # hash 1
%h2 = ('A'=>'Abc','B'=>'Bcd'); # hash 2
#arr = (\%h1,\%h2); # array of hash references.
foreach $hash_ref (#arr) { # iterate through the array.
foreach $key(keys %$hash_ref) { # iterate through the hash.
print $key.' '.$$hash_ref{$key}."\n"; #print key => value
}
}
In the interests of teaching you to fish, here's a link to the Perl data structures cookbook (perldsc) on building complex data structures in Perl.
You need a hash reference, as marked by { } below.
my #array = ({ k1 => "v1", k2 => 'v2' }, { K1 => 'V1', });