I have an array of objects which may contain objects with same attribute values. I am trying to remove the duplicates based on multiple attributes (not just one attribute value)
class Font
attr_accessor :color, :name, :type
end
a = <#color="blue", #name="s", #type="bold">
b = <#color="blue", #name="r", #type="italic">
c = <#color="green", #name="t", #type="bold">
d = <#color="blue", #name="s", #type="some_other_type">
fonts = [a, b, c, d]
I need to eliminate duplicates based on the values of color, name (I don't care about type)
what I have tried
uniq_fonts = fonts.uniq { |f| f.name.to_s + f.color.to_s}
is there any cleaner way in which I can achieve the same result?
Note:
these are objects and not hashes. I know we could have used:
fonts.uniq { |f| f.values_at(:name, :color)}
if they were hash
You can try:
uniq_fonts = fonts.uniq { |f| [ f.name, f.color ] }
You can defined your own values_at method like:
class Font
attr_accessor :color, :name, :type
def values_at *args
args.map { |method_name| self.public_send method_name }
end
end
And then do like :
fonts.uniq { |f| f.values_at(:name, :color)}
Related
I have two arrays, both filled with objects that have numerous attributes. Both arrays are holding the same object types. I want to find where objects match based on their attribute id
Object example:
#<Link:0x00007fac5eb6afc8 #id = 2002001, #length=40, #area='mars' ...>
Example arrays filled with objects:
array_links = [<Link:0x00007fac5eb6afc8>, <Link:0x00007fdf5eb7afc2>, <Link:0x000081dag6zb7agg8>... ]
selected_links = [<Link:0x00007fad8ob6gbh5>, <Link:0x00007fdg7hh4tif4>, <Link:0x000081dag7ij5bhh9>... ]
If these were strings of the object IDs and there was a match, I could use:
intersection = array_links & selected_links
However I want to do this based on their attribute and return a matching object itself.
Something like:
intersection = array_links.select(&:id) & selected_links.select(&:id)
But of course, not that, as that doesn't work, any ideas? :)
you can:
1 :
override the eql?(other) method then the array intersection will work
class Link < ApplicationRecord
def eql?(other)
self.class == other.class && self.id == other&.id # classes comparing class is a guard here
end
# you should always update the hash if you are overriding the eql?() https://stackoverflow.com/a/54961965/5872935
def hash
self.id.hash
end
end
2:
use array.select:
array_links.flat_map {|i| selected_links.select {|k| k.user_id == i.user_id }}
If they are the same object in memory, ie array_links = [<Link:0x123] and selected_links = [<Link:0x123>], then your solution of:
intersection = array_links & selected_links
Should work.
If they are not, you could loop over you array_links and select those which are in selected_links:
intersection = array_links.select do |link|
link.id.in? selected_links.map(&:id)
end
The result will be the same if you loop over selected_links and select those in array_links.
Depending on your resources and the size of these arrays, you could memoize selected_links.map(&:id) to prevent this from being re-built on each iteration.
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) }
My input is:
{"id"=>34, "fname"=>"XXX", "department"=>"IT", "email"=>"xxx#example.com"}
schema = [
{:type=>"string", :name=>"email", :control_type=>"email", :label=>"EMAIL"},
{:type=>"string", :name=>"fname", :control_type=>"text", :label=>"FNAME"},
{:type=>"string", :name=>"surname", :control_type=>"text", :label=>"LNAME"}
]
How do I pick only:
{"fname"=>"XXX", "email"=>"xxx#example.com"}
matching the :name of schema array?
Rails provides a Hash method that makes filtering quite simple - slice(). In combination with the splat operator * you could write the following code.
fields = schema.map { |e| e[:name] }
input.slice(*fields)
#=> {"fname"=>"XXX", "email"=>"xxx#example.com"}
You can first generate field names from schema
fields = schema.map{|f| f[:name]}
Then filter out your input:
input = {"id"=>34, "fname"=>"XXX", "department"=>"IT", "email"=>"xxx#example.com"}
input.select{|k, v| fields.include?(k)}
#=> {"fname"=>"XXX", "email"=>"xxx#example.com"}
So if a have this code:
class A
def initialize(type)
#type = type
end
end
instance = A.new(2)
another_instance = A.new(1)
array = [instance, another_instance]
is there a way to check if array includes an instance of A where #type is equal to a certain value? say, 2? like the include? method but where instead of checking for an instance of a certain class, it also checks the instance variables of that class?
I would recommend using anattr_reader for this one unless you plan on modifying the type somewhere after (in that case use attr_accessor which is both a writer and reader)
class A
attr_reader :type
def initialize(type)
#type = type
end
end
instance = A.new(2)
another_instance = A.new(1)
array = [instance, another_instance]
array.select do |item|
item.type == 2
end
=>[#<A:0x00000000dc3ea8 #type=2>]
Here I am iterating through an array of instances of A and selecting only the ones that meet the condition item.type == 2
You can just refer to the instance variable.
> array.any? { |item| item.is_a?(A) }
=> true
> array.any? { |item| item.instance_variable_get(:#type) == 1 }
=> true
> array.select { |item| item.instance_variable_get(:#type) == 1 }
=> [#<A:0x007fba7a12c6b8 #type=1>]
Or, use attr_accessor in your class, to make it way easier
class A
attr_accessor :type
def initialize(type)
#type = type
end
end
then you can do something = A.new(5); something.type
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.