Ruby의 객체 속성 별 Uniq


127

하나 이상의 속성과 관련하여 고유 한 배열에서 객체를 선택하는 가장 우아한 방법은 무엇입니까?

이러한 개체는 ActiveRecord에 저장되므로 AR의 방법을 사용하는 것도 좋습니다.

답변:


201

Array#uniq블록과 함께 사용 :

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

5
이것은 루비 1.9 이상 버전에 대한 정답입니다 .
nurettin

2
+1. 그리고 이전의 루비에는 항상 있습니다 require 'backports':-)
Marc-André Lafortune 2013

해시 방법은 num_plays를 합산하면서 album_id로 그룹화하려는 경우 더 좋습니다.
thekingoftruth

20
to_proc ( ruby-doc.org/core-1.9.3/Symbol.html#method-i-to_proc )을 사용하여 개선 할 수 있습니다 .@photos.uniq &:album_id
joaomilho 2013

당신이 필요로 루비 1.8 @brauliobo이 같은 SO에서 바로 아래 읽기 : stackoverflow.com/a/113770/213191
피터 H. Boling에게

22

uniq_by프로젝트의 Array에 메서드를 추가합니다 . .NET과 유사하게 작동합니다 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

1
좋은 API이지만 큰 배열의 경우 성능이 좋지 않습니다 (O (n ^ 2)처럼 보임). 변환을 해시 세트로 만들어 수정할 수 있습니다.
tribalvibes

7
이 답변은 오래되었습니다. Ruby> = 1.9에는 수락 된 답변에서와 같이 정확히 이것을 수행하는 블록이있는 Array # uniq가 있습니다.
피터 H. Boling

17

데이터베이스 수준에서 수행하십시오.

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

1
관심 분야가 두 개 이상이라면 어떨까요?
Ryan Bigg

12

이 트릭을 사용하여 배열에서 여러 속성 요소별로 고유 한 항목을 선택할 수 있습니다.

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

너무 뻔 하니까 루비. 그냥 다른 이유는 루비 축복
ToTenMilan

6

나는 원래 selectArray 에서 방법을 사용하도록 제안했습니다 . 재치 :

[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

고유성을 강화하기 위해 jmah의 해시 사용을 좋아합니다. 그 고양이를 피부로 가꾸는 몇 가지 방법이 더 있습니다.

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

그것은 좋은 1 라이너이지만 이것이 조금 더 빠를 수 있다고 생각합니다.

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

3

귀하의 질문을 올바르게 이해했다면 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

3

내가 찾은 가장 우아한 방법 Array#uniq은 블록과 함께 사용하는 스핀 오프 입니다.

enumerable_collection.uniq(&:property)

… 그것은 또한 더 잘 읽습니다!


2

각 키에 대해 하나의 값만 포함하는 해시를 사용할 수 있습니다.

Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].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

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 속성에 대한 것이지만 사전 순 정렬로 동일한 작업을 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.