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

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

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

+116
источник поделиться
14 ответов

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

@photos = @photos.uniq { |p| p.album_id }
+190
источник

Добавьте метод uniq_by в Array в свой проект. Он работает по аналогии с 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!, но он должен быть достаточно простым, если вы хотите.

EDIT: 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
+22
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

YourModel.find(:all, :group => "status")
+17
источник

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

@photos = @photos.uniq { |p| [p.album_id, p.author_id] }
+10
источник

Я изначально предложил использовать метод 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.

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

+6
источник

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

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

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

h = {}
objs.each {|e| h[e.attr]=e}
h.values
+5
источник

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

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
+3
источник

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

enumerable_collection.uniq(&:property)

... он тоже читает!

+3
источник

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

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
+2
источник

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

+2
источник

Мне нравятся ответы jmah и Head. Но сохраняют ли они порядок массивов? Они могут быть в более поздних версиях ruby, поскольку в спецификации языка были записаны некоторые требования к сохранению порядка хеш-букв, но здесь похожее решение, которое мне нравится использовать, сохраняет порядок независимо.

h = Set.new
objs.select{|el| h.add?(el.attr)}
+1
источник

Реализация ActiveSupport:

def uniq_by
  hash, array = {}, []
  each { |i| hash[yield(i)] ||= (array << i) }
  array
end
+1
источник

Теперь, если вы можете сортировать значения атрибутов, это можно сделать:

class A
  attr_accessor :val
  def initialize(v); self.val = v; end
end

objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}

objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
  uniqs << a if uniqs.empty? || a.val != uniqs.last.val
  uniqs
end

Что для 1-атрибута уникально, но то же самое можно сделать с лексикографическим видом...

0
источник

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

objects.uniq {|obj| obj.attribute}

Или более краткий подход:

objects.uniq(&:attribute)
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос