Uniq по атрибуту объекта в Ruby - PullRequest
       68

Uniq по атрибуту объекта в Ruby

112 голосов
/ 21 сентября 2008

Какой самый элегантный способ выделить объекты в массиве, которые являются уникальными по одному или нескольким атрибутам?

Эти объекты хранятся в ActiveRecord, поэтому было бы неплохо использовать методы AR.

Ответы [ 13 ]

181 голосов
/ 10 апреля 2012

Используйте Array#uniq с блоком:

@photos = @photos.uniq { |p| p.album_id }
21 голосов
/ 22 сентября 2008

Добавьте метод uniq_by в массив в вашем проекте. Работает по аналогии с sort_by. Таким образом, uniq_by равно uniq, как sort_by равно sort. Использование:

uniq_array = my_array.uniq_by {|obj| obj.id}

Реализация:

class Array
  def uniq_by(&blk)
    transforms = []
    self.select do |el|
      should_keep = !transforms.include?(t=blk[el])
      transforms << t
      should_keep
    end
  end
end

Обратите внимание, что он возвращает новый массив, а не изменяет ваш текущий на месте. Мы не написали uniq_by! метод, но он должен быть достаточно простым, если вы хотите.

РЕДАКТИРОВАТЬ: Tribalvibes указывает, что эта реализация O (n ^ 2). Лучше было бы что-то вроде (не проверено) ...

class Array
  def uniq_by(&blk)
    transforms = {}
    select do |el|
      t = blk[el]
      should_keep = !transforms[t]
      transforms[t] = true
      should_keep
    end
  end
end
16 голосов
/ 21 сентября 2008

Сделайте это на уровне базы данных:

YourModel.find(:all, :group => "status")
7 голосов
/ 08 июня 2016

Вы можете использовать этот трюк для выбора уникальных по нескольким атрибутам элементов из массива:

@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
6 голосов
/ 21 сентября 2008

Я изначально предлагал использовать метод select на массиве. Для остроумия:

[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0} возвращает нам [2,4,6].

Но если вы хотите первый такой объект, используйте detect.

[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} дает нам 4.

Я не уверен, что вы собираетесь сюда, хотя.

5 голосов
/ 24 октября 2008

Мне нравится, что jmah использует Hash для обеспечения уникальности. Вот еще пара способов снять шкуру с этой кошки:

objs.inject({}) {|h,e| h[e.attr]=e; h}.values

Это хороший 1-лайнер, но я подозреваю, что это может быть немного быстрее:

h = {}
objs.each {|e| h[e.attr]=e}
h.values
3 голосов
/ 21 сентября 2017

Самый элегантный способ, который я нашел, - это выделение с использованием Array#uniq с блоком

enumerable_collection.uniq(&:property)

... это тоже читается лучше!

3 голосов
/ 21 сентября 2008

Если я правильно понимаю ваш вопрос, я решил эту проблему, используя квази-хакерский подход сравнения объектов Marshaled, чтобы определить, отличаются ли какие-либо атрибуты. Пример внедрения в конце следующего кода:

class Foo
  attr_accessor :foo, :bar, :baz

  def initialize(foo,bar,baz)
    @foo = foo
    @bar = bar
    @baz = baz
  end
end

objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]

# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
  if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
    uniqs << obj
  end
  uniqs
end
2 голосов
/ 10 мая 2011

В Rails также есть метод #uniq_by - см. Параметризованный массив # uniq (т.е. uniq_by)

2 голосов
/ 21 сентября 2008

Вы можете использовать хеш, который содержит только одно значение для каждого ключа:

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
...