How to remove duplicates in a hash in Ruby on Rails?

后端 未结 4 1647
眼角桃花
眼角桃花 2020-12-15 23:39

I have a hash like so:

[
  {
    :lname => \"Brown\",
    :email => \"james@intuit.com\",
    :fname => \"James\"
  },
  {
    :lname => nil,
            


        
相关标签:
4条回答
  • 2020-12-16 00:18

    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"}]
    
    0 讨论(0)
  • 2020-12-16 00:27

    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] }
    
    0 讨论(0)
  • 2020-12-16 00:28

    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
    
    0 讨论(0)
  • 2020-12-16 00:32

    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 }
    
    0 讨论(0)
提交回复
热议问题