3

What if instead of removing duplicate elements from an array, I want to remove elements that have a specific property in common?

Specifically, I want to remove all strings from an array with duplicate "essences", where essence is defined like this:

class String
  def essence
    downcase.gsub('&', 'and').gsub(/[^a-z0-9]/, '')
  end
end

I want something like this:

['a', 'A', 'b'].uniq_by(&:essence)
# => ['a', 'b'] (or ['A', 'b']; I don't really care)

What's the best way to accomplish this?

Tom Lehman
  • 85,973
  • 71
  • 200
  • 272

3 Answers3

10

Since 1.9.2, Array#uniq (and uniq!) takes a block, so no more need for uniq_by.

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
steenslag
  • 79,051
  • 16
  • 138
  • 171
4

Activesupport has a Array#uniq_by, and this is the code:

class Array
  def uniq_by
    hash, array = {}, []
    each { |i| hash[yield(i)] ||= (array << i) }
    array
  end
end

Facets also has a Enumerable#uniq_by:

module Enumerable    
  def uniq_by
    h = {}
    inject([]) {|a,x| h[yield(x)] ||= a << x}
  end    
end
tokland
  • 66,169
  • 13
  • 144
  • 170
3

Quick and dirty way:

['a', 'A', 'b'].group_by {|i| i.essence}.values.map(&:first)

And some monkey patching:

class Array
  def uniq_by(&block)
    group_by(&block).values.map(&:first)
  end
end
Tomas
  • 704
  • 5
  • 10
  • Hey, just wanted to comment that you can actually do `group_by(&:essence)` so that if you wish to update your answer you can! – Jordon Bedwell Apr 13 '16 at 22:06