Return similar elements of array in Ruby - arrays

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"]

Related

Ruby iterating array of dictionaries

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) }

How to get specific keys in Array of Hashes?

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")

How to iterate through an array of hashes sorting the keys in Ruby

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 }

How do I look up a value in a hash based on an array of keys?

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

Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays

A web service is returning a hash that contains an unknown number of nested hashes, some of which contain an array, which in turn contains an unknown number of nested hashes.
Some of the keys are not unique -- i.e. are present in more than one of the nested hashes.
However, all the keys that I actually care about are all unique.
Is there someway I can give a key to the top-level hash, and get back it's value even if the key-value pair is buried deep in this morass?
(The web service is Amazon Product Advertising API, which slightly varies the structure of the results that it gives depending on the number of results and the search types permitted in each product category.)
Here's a simple recursive solution:
def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42
No need for monkey patching, just use Hashie gem: https://github.com/intridea/hashie#deepfind
user = {
name: { first: 'Bob', last: 'Boberts' },
groups: [
{ name: 'Rubyists' },
{ name: 'Open source enthusiasts' }
]
}
user.extend Hashie::Extensions::DeepFind
user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' }
For arbitrary Enumerable objects, there is another extension available, DeepLocate: https://github.com/intridea/hashie#deeplocate
Combining a few of the answers and comments above:
class Hash
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.is_a? Enumerable
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
Ruby 2.3 introduces Hash#dig, which allows you to do:
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
A variation of barelyknown's solution: This will find all the values for a key in a hash rather than the first match.
class Hash
def deep_find(key, object=self, found=[])
if object.respond_to?(:key?) && object.key?(key)
found << object[key]
end
if object.is_a? Enumerable
found << object.collect { |*a| deep_find(key, a.last) }
end
found.flatten.compact
end
end
{a: [{b: 1}, {b: 2}]}.deep_find(:b) will return [1, 2]
Despite this appearing to be a common problem, I've just spent a while trying to find/come up with exactly what I need, which I think is the same as your requirement. Neither of the links in the first response are spot-on.
class Hash
def deep_find(key)
key?(key) ? self[key] : self.values.inject(nil) {|memo, v| memo ||= v.deep_find(key) if v.respond_to?(:deep_find) }
end
end
So given:
hash = {:get_transaction_list_response => { :get_transaction_list_return => { :transaction => [ { ...
The following:
hash.deep_find(:transaction)
will find the array associated with the :transaction key.
This is not optimal as the inject will continue to iterate even if memo is populated.
I use the following code
def search_hash(hash, key)
return hash[key] if hash.assoc(key)
hash.delete_if{|key, value| value.class != Hash}
new_hash = Hash.new
hash.each_value {|values| new_hash.merge!(values)}
unless new_hash.empty?
search_hash(new_hash, key)
end
end
I ended up using this for a small trie search I wrote:
def trie_search(str, obj=self)
if str.length <= 1
obj[str]
else
str_array = str.chars
next_trie = obj[str_array.shift]
next_trie ? trie_search(str_array.join, next_trie) : nil
end
end
Note: this is just for nested hashes at the moment. Currently no array support.
Because Rails 5 ActionController::Parameters no longer inherits from Hash, I've had to modify the method and make it specific to parameters.
module ActionController
class Parameters
def deep_find(key, object=self, found=nil)
if object.respond_to?(:key?) && object.key?(key)
return object[key]
elsif object.respond_to?(:each)
object = object.to_unsafe_h if object.is_a?(ActionController::Parameters)
object.find { |*a| found = deep_find(key, a.last) }
return found
end
end
end
end
If the key is found, it returns the value of that key, but it doesn't return an ActionController::Parameter object so Strong Parameters are not preserved.

Resources