How to remove duplicates in a hash in Ruby on Rails? - ruby-on-rails

I have a hash like so:
[
{
:lname => "Brown",
:email => "james#intuit.com",
:fname => "James"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
},
{
:lname => "Smith",
:email => "brad#intuit.com",
:fname => "Brad"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
},
{
:lname => "Smith",
:email => "brad#intuit.com",
:fname => "Brad"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
}
]
What I would like to learn how to do is how to remove a record if it is duplicate. Meaning, see how there are several "brad#intuit.com" how can I remove the duplicate records, meaning remove all the others that have an email of "brad#intuit.com".... Making email the key not the other fields?

In Ruby 1.9.2, Array#uniq will accept a block paramater which it will use when comparing your objects:
arrays.uniq { |h| h[:email] }

I know this is an old thread, but Rails has a method on 'Enumerable' called 'index_by' which can be handy in this case:
list = [
{
:lname => "Brown",
:email => "james#intuit.com",
:fname => "James"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
},
{
:lname => "Smith",
:email => "brad#intuit.com",
:fname => "Brad"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
},
{
:lname => "Smith",
:email => "brad#intuit.com",
:fname => "Brad"
},
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
}
]
Now you can get the unique rows as follows:
list.index_by {|r| r[:email]}.values
To merge the rows with the same email id.
list.group_by{|r| r[:email]}.map do |k, v|
v.inject({}) { |r, h| r.merge(h){ |key, o, n| o || n } }
end
Custom but efficient method:
list.inject({}) do |r, h|
(r[h[:email]] ||= {}).merge!(h){ |key, old, new| old || new }
r
end.values

If you're putting this directly into the database, just use validates_uniqueness_of :email in your model. See the documentation for this.
If you need to remove them from the actual hash before being used then do:
emails = [] # This is a temporary array, not your results. The results are still in my_array
my_array.delete_if do |item|
if emails.include? item[:email]
true
else
emails << item[:email]
false
end
end
UPDATE:
This will merge the contents of duplicate entries
merged_list = {}
my_array.each do |item|
if merged_list.has_key? item[:email]
merged_list[item.email].merge! item
else
merged_list[item.email] = item
end
end
my_array = merged_list.collect { |k, v| v }

Ok, this (delete duplicates) is what you asked for:
a.sort_by { |e| e[:email] }.inject([]) { |m,e| m.last.nil? ? [e] : m.last[:email] == e[:email] ? m : m << e }
But I think this (merge values) is what you want:
a.sort_by { |e| e[:email] }.inject([]) { |m,e| m.last.nil? ? [e] : m.last[:email] == e[:email] ? (m.last.merge!(e) { |k,o,n| o || n }; m) : m << e }
Perhaps I'm stretching the one-liner idea a bit unreasonably, so with different formatting and a test case:
Aiko:so ross$ cat mergedups
require 'pp'
a = [{:fname=>"James", :lname=>"Brown", :email=>"james#intuit.com"},
{:fname=>nil, :lname=>nil, :email=>"brad#intuit.com"},
{:fname=>"Brad", :lname=>"Smith", :email=>"brad#intuit.com"},
{:fname=>nil, :lname=>nil, :email=>"brad#intuit.com"},
{:fname=>"Brad", :lname=>"Smith", :email=>"brad#intuit.com"},
{:fname=>"Brad", :lname=>"Smith", :email=>"brad#intuit.com"}]
pp(
a.sort_by { |e| e[:email] }.inject([]) do |m,e|
m.last.nil? ? [e] :
m.last[:email] == e[:email] ? (m.last.merge!(e) { |k,o,n| o || n }; m) :
m << e
end
)
Aiko:so ross$ ruby mergedups
[{:email=>"brad#intuit.com", :fname=>"Brad", :lname=>"Smith"},
{:email=>"james#intuit.com", :fname=>"James", :lname=>"Brown"}]

Related

How to convert mongoid criteria to array?

I have a mongoid criteria categories and I need to convert to an array. I'm using categories.to_a but this dont works and always that the mongoid criteria is iterate by .map it's doing a .find a new query.
How can I fix this?
def self.mapOffers (array, user)
array.map { |u|
{
:id => u.id.to_s,
:name => u.name,
:description => u.description,
:price => u.price,
:url => u.url,
:categories => Category.mapCategories(u.categories.to_a, user),
:picture => u.picture.url,
:accepts_cash => u.accepts_cash_transactions,
:location => {
:longitude => u.longitude,
:latitude => u.latitude,
:street => u.street,
:neighborhood => u.neighborhood,
:number => u.number,
:zip => u.zip,
:city => u.city,
:state => u.state,
:complement => u.complement,
:country => u.country,
},
:fixedMeetingPoint => u.fixedMeetingPoint,
:meetingPoint => {
:street => u.meetingPointStreet,
:neighborhood => u.meetingPointNeighborhood,
:number => u.meetingPointNumber,
:zip => u.meetingPointZip,
:city => u.meetingPointCity,
:state => u.meetingPointState,
:complement => u.meetingPointComplement,
:country => u.meetingPointCountry,
:latitude => u.meetingPointLatitude,
:longitude => u.meetingPointLongitude,
},
:notes => u.notes,
}}
end
def self.mapCategories (array, user)
array.map { |u| {
:id => u.id.to_s,
:name => u.name,
:selected => !user.nil? && u.users.include?(user),
:picture => u.picture.url,
}}
end
Starting from criteria:
scope = Band.where(name: 'foo')
... retrieve the complete result set from the database and store in an array:
bands = scope.to_a
... then iterate the array any number of times:
bands.each { |band| ... }
bands.each { |band| ... }

ARRAY ACCESS AND INSERTION RECORD DATABASE

I would need to access the array to retrieve the information and insert it into the database (Code, Customer, Phone1, Phone2). Someone can help me?
{
:recordset => {
:row => [
[0] {
:property => [
[0] {
:name => "Code",
:value => "C0001"
},
[1] {
:name => "Customer",
:value => "ROSSI MARIO"
},
[2] {
:name => "Phone1",
:value => "1234567890"
}
]
},
[1] {
:property => [
[0] {
:name => "Code",
:value => "C0002"
},
[1] {
:name => "Customer",
:value => "VERDE VINCENT"
},
[2] {
:name => "Phone1",
:value => "9876543210"
},
[3] {
:name => "Phone2",
:value => "2468101214"
}
]
}
]
},
:#xmlns => "http://localhost/test"
}
p.s. The Phone2 value during the SOAP call is only displayed if it is present in an archive
Thank you
Your data doesn't look like a valid ruby structure. Once you are able to convert it to be more ruby-like, you can make use of each_with_object to generate a well formatted set of attributes and their values from your data:
data
=> {:recordset=>{
:row=>[{
:property=>[
{:name=>"Code", :value=>"C0001"},
{:name=>"Customer", :value=>"ROSSI MARIO"},
{:name=>"Phone1", :value=>"1234567890"}
]
}, {
:property=>[
{:name=>"Code", :value=>"C0002"},
{:name=>"Customer", :value=>"VERDE VINCENT"},
{:name=>"Phone1", :value=>"9876543210"},
{:name=>"Phone2", :value=>"2468101214"}
]
}]
},
:#xmlns=>"http://localhost/test"}
data.keys
=> [:recordset, :#xmlns]
data[:recordset][:row].count
=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
=> [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
{"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}]
Now, you can iterate over result and create respective records in database.

Rails: Values not getting assigned to array when in a do loop

I have an array called components
components = [
{
:name => "Component 1",
:order => "1"
},
{
:name => "Component 2",
:order => "2"
},
{
:name => "Component 3",
:order => "3"
},
{
:name => "Component 4",
:order => "4"
}
]
And another array called subcomponent
subcomponents = [
{
:name => "Subcomponent 1A",
:order => "1A"
},
{
:name => "Subomponent 1B",
:order => "1B"
},
{
:name => "Component 2A",
:order => "2A"
},
{
:name => "Component 4A",
:order => "4A"
}
]
I'm trying to get it so that the subcomponents appear underneath a component object if they are a subcomponent of that object. This is the expected output:
components = [
{
:name => "Component 1",
:order => "1",
:subcomponents => [
{
:name => "Subcomponent 1A",
:order => "1A"
},
{
:name => "Subomponent 1B",
:order => "1B"
}
]
},
{
:name => "Component 2",
:order => "2",
:subcomponents => [
{
:name => "Component 2A",
:order => "2A"
}
]
},
{
:name => "Component 3",
:order => "3",
:subcomponent => []
},
{
:name => "Component 4",
:order => "4",
:subcomponent => [
{
:name => "Component 4A",
:order => "4A"
}
]
}
]
I've created a loop to try to do this:
components.each do |c|
c.class.module_eval { attr_accessor :subcomponents}
c.subcomponents = []
subcomponents.each do |s|
if /#{c["order"]}[A-Z]/ =~ s["order"]
#This is never assigned but it does make it into this statement
c.subcomponents << s
end
end
puts c.subcomponents.to_s # This prints []
end
As I've put in my code comments, the subcomponent is never assigned back to the component.subcomponents arrays even though the if statement is stepped into.
Am I missing something where the scope of component.subcomponents isn't accessible anymore or is at a different scope?
What should I be doing to ensure I can assign value component.subcomponents?
components.each do |c|
c[:subcomponents] = []
subcomponents.each do |s|
c[:subcomponents] << s if /#{c[:order]}[A-Z]/ =~ s[:order]
end
end
Hope this work.
components.each do |component|
component[:subcomponents] = []
end
subcomponents.each do |subcomponent|
order = subcomponent[:order][/\d+/]
components.each do |component|
if order == component[:order]
component[:subcomponents] << subcomponent
end
end
end
You can do it in O(n) using this:
subcomponent_hash = Hash.new { |h, k| h[k] = [] }
subcomponents.each do |sc|
order = sc[:order][/\d+/]
subcomponent_hash[order] << sc
end
components.each do |c|
c[:subcomponents] = subcomponent_hash[c[:order]]
end

Rails how to group by nested hash

I have no clue to solve this question. I got a nested hash inside an array called data. Here is its structure.
data =
[
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 20,
:name => "S20"
}
]
}
]
},
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 21,
:name => "S21"
}
]
}
]
},
{
:id => 1,
:name => "S1",
:children => [
{
:id => 11,
:name => "S11",
:children => [
{
:id => 22,
:name => "S22"
}
]
}
]
}
]
As you can see, there are bunch of elements which have the same id in the first layer or second layer, so I need to group them.
I hope the result will be
result=
[
{
:id => 1,
:name => "S1",
:children => [
{
:id => 10,
:name => "S10",
:children => [
{
:id => 20,
:name => "S20"
},
{
:id => 21,
:name => "S21"
}
]
},
{
:id => 11,
:name => "S11",
:children => [
{
:id => 22,
:name => "S22"
}
]
}
]
}
]
I've tried somthing like
data.group_by{|s| s[:id]}
However, it would only group the first layer, I don't know how to group nested structure.
Yeah you need some kind of recursive method to recursively combine and group the nested children.
This produces the result you want:
def group(data)
r = {}
# Combine the children
data.each do |d|
if r[d[:id]]
r[d[:id]][:children] += d[:children]
else
r[d[:id]] = d
end
end
# Now group the children
r.values.map do |d|
d[:children] = group(d[:children]) if d[:children]
d
end
end

ruby/rails - Given two Hashes how to exchange items?

I have the following hashes:
#valids
[
{
:lname => "Brown",
:email => "james#intuit.com",
:fname => "James"
},
{
:lname => "Smith",
:email => "brad#intuit.com",
:fname => "Brad"
}
]
#invalids
[
{
:lname => nil,
:email => "brad#intuit.com",
:fname => nil
},
{
:lname => nil,
:email => "roger#gmail.com",
:fname => nil
}
]
What I'm going to be doing is looping through the invalids, and if an email meets a certain criteria, I want to move that item to valids and then remove it from invalids.
Example, while looping through #invalids, if the email = roger#gmail.com, I want to take:
{
:lname => nil,
:email => "roger#gmail.com",
:fname => nil
}
And move it to #valids, and remove it from #invalids.
Is there a way to do this without having to create new hashes? Thanks
This should do it:
#invalids = #invalids.reject do |record|
if record[:email] == "roger#gmail.com"
#valids.push(record)
end
end
To explain it a little bit, I'm setting #invalids as the result of running
#invalids.reject and passing it a block, so it will reject any array item that meets the criteria (returns something truish).
Inside the conditional I add to the #valids array

Resources