2008-09-20 3 views
100

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

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

ответ

156

Использование Array#uniq с блоком: осуществление

@photos = @photos.uniq { |p| p.album_id } 
6

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

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

+0

+1 Для обнаружения метода, никогда не осознающего этого. – pierrotlefou 2009-10-13 07:12:44

3

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

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 
0

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

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-атрибута уникально, но то же самое можно сделать и ж/лексикографическом рода ...

13

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

YourModel.find(:all, :group => "status") 
+1

и что, если это было более одного поля, из интереса? – 2008-09-21 07:08:20

2

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

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

Добавить метод 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!, но это было бы достаточно легко, если бы вы этого хотели.

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 
+1

Хороший api, но это будет плохо (выглядит как O (n^2)) масштабируемость для больших массивов. Может быть исправлено, делая преобразования хешсет. – tribalvibes 2010-10-27 06:56:15

+6

Этот ответ устарел. Ruby> = 1.9 имеет Array # uniq с блоком, который делает именно это, как в принятом ответе. – 2014-12-24 01:48:34

5

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

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

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

h = {} 
objs.each {|e| h[e.attr]=e} 
h.values 
1

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

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 
4

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

@photos = @photos.uniq { |p| [p.album_id, p.author_id] } 
0

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

enumerable_collection.uniq(&:property) 

... он читает лучше тоже!