Ruby 배열에서 동일한 문자열 요소를 계산하는 방법


91

나는 다음이있다 Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

동일한요소에 대한 개수를 어떻게 생성 합니까?

Where:
"Jason" = 2, "Judah" = 3, "Allison" = 1, "Teresa" = 1, "Michelle" = 1?

또는 해시를 생성합니다 .

위치 : hash = { "Jason"=> 2, "Judah"=> 3, "Allison"=> 1, "Teresa"=> 1, "Michelle"=> 1}


2
Ruby 2.7부터 Enumerable#tally. 여기에 더 많은 정보가 있습니다 .
SRack

답변:


82
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = Hash.new(0)
names.each { |name| counts[name] += 1 }
# => {"Jason" => 2, "Teresa" => 1, ....

127
names.inject(Hash.new(0)) { |total, e| total[e] += 1 ;total}

당신에게 준다

{"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1} 

3
+1 선택한 답변과 비슷하지만 "외부"변수없이 인 젝트 사용을 선호합니다.

18
each_with_object대신 사용 하는 경우 블록에서 inject( ;total) 를 반환 할 필요가 없습니다 .
mfilej 2013-09-24

12
후손에게는 @mfilej가 의미하는 바입니다.array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
Gon Zifroni

2
Ruby 2.7에서는 다음을 수행 할 수 있습니다 names.tally..
Hallgeir Wilhelmsen

99

Ruby v2.7 이상 (최신)

(2019 12월 발표) 루비 v2.7.0로, 핵심 언어는 이제 포함 Enumerable#tally- 새로운 방법 이 문제를 위해 특별히 설계된를 :

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.tally
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.4 이상 (현재 지원되지만 이전 버전)

이 질문이 처음 질문되었을 때 (2011 년 2 월) 표준 루비에서는 다음 코드를 사용할 수 없었습니다.

  • Object#itself, Ruby v2.2.0 (2014 년 12 월 출시)에 추가되었습니다.
  • Hash#transform_values, 이는 Ruby v2.4.0 (2016 년 12 월 출시)에 추가되었습니다.

Ruby에 대한 이러한 최신 추가 기능을 통해 다음 구현이 가능합니다.

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

names.group_by(&:itself).transform_values(&:count)
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

Ruby v2.2 이상 (지원 중단됨)

위에서 언급 한 Hash#transform_values방법에 액세스하지 않고 이전 루비 버전을 사용하는 경우 대신 Array#to_hRuby v2.1.0 (2013 년 12 월 릴리스)에 추가 된을 사용할 수 있습니다 .

names.group_by(&:itself).map { |k,v| [k, v.length] }.to_h
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

이전 루비 버전 ( <= 2.1)의 경우이를 해결할 수있는 여러 가지 방법이 있지만 (제 생각에는) 명확한 "최상의"방법은 없습니다. 이 게시물에 대한 다른 답변을 참조하십시오.


게시하려고 했어요 : P. / count대신 사용하는 것 사이에 눈에 띄는 차이점이 있습니까? sizelength
ice ツ

1
@SagarPandya 아니요, 차이가 없습니다. 달리 Array#size하고 Array#length, Array#count 선택적 인수 또는 블록을; 그러나 둘 다 사용하지 않으면 구현이 동일합니다. 보다 구체적으로, 세 가지 방법은 전화 LONG2NUM(RARRAY_LEN(ary))후드 : 카운트 / 길이
톰 주

1
이것은 관용적 인 Ruby의 좋은 예입니다. 좋은 대답입니다.
slhck

1
추가 크레딧! 개수로 정렬.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
Abram

2
@Abram 당신은 할 수 있습니다 sort_by{ |k, v| -v}, reverse필요 없습니다 ! ;-)
Sony Santos

26

이제 Ruby 2.2.0을 사용하여 itself방법을 활용할 수 있습니다 .

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
counts = {}
names.group_by(&:itself).each { |k,v| counts[k] = v.length }
# counts > {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

3
동의하지만, 약간 선호합니다 names.group_by (& : itself) .map {| k, v | . k는, v.count]} 적 해시 객체 선언 그래서 당신이하지 않아도 to_h
앤디 날

8
@andrewkday 한 단계 더 나아가 ruby ​​v2.4는 다음과 같은 방법을 추가했습니다.이 방법 Hash#transform_values을 사용하면 코드를 훨씬 더 단순화 할 수 있습니다.names.group_by(&:itself).transform_values(&:count)
Tom Lord

또한 이것은 매우 미묘한 요점 (더 이상 향후 독자와 관련이 없을 가능성이 높습니다!)이지만 코드도 Array#to_hRuby v2.1.0 (2013 년 12 월 출시됨)에 추가되었습니다. 즉, 원래 질문이 나온 지 거의 3 년 후 질문되었습니다!)
Tom Lord

17

실제로이를 수행하는 데이터 구조가 MultiSet있습니다..

불행히도 MultiSetRuby 코어 라이브러리 또는 표준 라이브러리 에는 구현 이 없지만 웹 주위에 떠 다니는 두 가지 구현이 있습니다.

이것은 데이터 구조의 선택이 알고리즘을 단순화 할 수있는 방법을 보여주는 좋은 예입니다. 실제로이 특정 예에서는 알고리즘이 완전히 사라집니다. 말 그대로 다음과 같습니다.

Multiset.new(*names)

그리고 그게 다야. https://GitHub.Com/Josh/Multimap/ 사용 예 :

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset.new(*names)
# => #<Multiset: {"Jason", "Jason", "Teresa", "Judah", "Judah", "Judah", "Michelle", "Allison"}>

histogram.multiplicity('Judah')
# => 3

http://maraigue.hhiro.net/multiset/index-en.php 사용 예 :

require 'multiset'

names = %w[Jason Jason Teresa Judah Michelle Judah Judah Allison]

histogram = Multiset[*names]
# => #<Multiset:#2 'Jason', #1 'Teresa', #3 'Judah', #1 'Michelle', #1 'Allison'>

MultiSet 개념은 수학 또는 다른 프로그래밍 언어에서 비롯된 것입니까?
Andrew Grimm

2
@Andrew Grimm : 그는 "multiset" 라는 단어 (de Bruijn, 1970s)와 개념 (Dedekind 1888)은 모두 수학에서 유래했습니다. Multiset엄격한 수학적 규칙에 의해 관리되고 일부 중요한 법칙이 수행하지만 "정상"수학적 집합 이론의 공리, 법칙 및 정리와 대부분 일치 하는 방식으로 일반적인 집합 연산 (결합, 교차, 보완 등)을 지원합니다. 다중 세트로 일반화하려고 할 때 유지 되지 않습니다 . 그러나 그것은 문제에 대한 나의 이해를 넘어서는 방법입니다. 나는 그것들을 수학적 개념이 아닌 프로그래밍 데이터 구조로 사용합니다.
Jörg W Mittag

그 점을 조금 더 확장하려면 : "... 공리와 거의 일치하는 방식으로 ..." : "정상"집합은 일반적으로 "제르 멜로-프랑 켈 집합 이론"이라는 공리 집합 (가정)에 의해 공식적으로 정의됩니다. ". 그러나 이러한 공리 중 하나 : 확장 성의 공리 는 세트가 구성원에 의해 정확하게 정의됨을 나타냅니다 {A, A, B} = {A, B}. 이것은 분명히 다중 세트의 정의를 위반하는 것입니다!
Tom Lord

... 그러나 너무 자세하게 다루지 않고 (이것이 고급 수학이 아닌 소프트웨어 포럼이기 때문에!) Crisp 세트, Peano 공리 및 기타 MultiSet 관련 공리를 통해 수학적으로 다중 세트를 공식적으로 정의 할 수 있습니다 .
Tom Lord

13

Enumberable#each_with_object 최종 해시를 반환하지 않아도됩니다.

names.each_with_object(Hash.new(0)) { |name, hash| hash[name] += 1 }

보고:

=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

동의는, each_with_object변형보다 나에게 더 읽을inject
레프 Lukomsky

9

Ruby 2.7 이상

Enumerable#tally이 정확한 목적을 위해 Ruby 2.7이 도입 되었습니다. 여기에 좋은 요약이 있습니다 .

이 사용 사례에서 :

array.tally
# => { "Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1 }

출시되는 기능에 대한 문서는 여기에 있습니다 .

이것이 누군가를 돕기를 바랍니다!


환상적인 소식!
tadman

6

작동합니다.

arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
result = {}
arr.uniq.each{|element| result[element] = arr.count(element)}

2
이 악화 이론적 복잡성을 가지고 있지만 - - 다른 접근 방법에 대한 +1 O(n^2)(일부 값을 중요합니다 n) 추가 작업을 수행을 (그것은 예를 들어 "유다"배를위한 계산한다)! 나는 또한 (지도 결과가 삭제되고 있음) each대신 제안 합니다map

감사합니다! 맵을 각각으로 변경했고, 배열을 살펴보기 전에 통합했습니다. 이제 복잡성 문제가 해결 되었을까요?
Shreyas

6

다음은 약간 더 기능적인 프로그래밍 스타일입니다.

array_with_lower_case_a = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
hash_grouped_by_name = array_with_lower_case_a.group_by {|name| name}
hash_grouped_by_name.map{|name, names| [name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

의 한 가지 장점은 group_by동일한 항목을 그룹화하는 데 사용할 수 있다는 것입니다.

another_array_with_lower_case_a = ["Jason", "jason", "Teresa", "Judah", "Michelle", "Judah Ben-Hur", "JUDAH", "Allison"]
hash_grouped_by_first_name = another_array_with_lower_case_a.group_by {|name| name.split(" ").first.capitalize}
hash_grouped_by_first_name.map{|first_name, names| [first_name, names.length]}
=> [["Jason", 2], ["Teresa", 1], ["Judah", 3], ["Michelle", 1], ["Allison", 1]]

함수형 프로그래밍을 들었습니까? +1 :-) 이것은 메모리 효율적이지 않다고 주장 할 수 있지만 확실히 최선의 방법입니다. Facets에는 Enumerable # frequency가 있습니다.
tokland

5
a = [1, 2, 3, 2, 5, 6, 7, 5, 5]
a.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }

# => {1=>1, 2=>2, 3=>1, 5=>3, 6=>1, 7=>1}

신용 Frank Wambutt


3
names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]
Hash[names.group_by{|i| i }.map{|k,v| [k,v.size]}]
# => {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

2

여기에 많은 훌륭한 구현이 있습니다.

그러나 초보자로서 나는 이것이 읽고 구현하기 가장 쉬운 것이라고 생각할 것입니다.

names = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

name_frequency_hash = {}

names.each do |name|
  count = names.count(name)
  name_frequency_hash[name] = count  
end
#=> {"Jason"=>2, "Teresa"=>1, "Judah"=>3, "Michelle"=>1, "Allison"=>1}

우리가 취한 조치 :

  • 우리는 해시를 만들었습니다.
  • 우리는 names배열 을 반복했습니다.
  • names배열 에 각 이름이 몇 번이나 나타 났는지
  • 우리는을 사용하여 키를 생성 name하고 값을 사용하여count

약간 더 장황 할 수 있지만 (성능면에서는 키를 재정 의하여 불필요한 작업을 수행하게 될 것입니다), 제 생각에는 달성하려는 것을 읽고 이해하기가 더 쉽습니다.


2
나는 그것이 받아 들인 대답보다 얼마나 읽기 쉬운 지 알지 못하며 분명히 더 나쁜 디자인입니다 (많은 불필요한 작업을 수행함).
Tom Lord

@Tom Lord-성능에 대해 동의합니다 (내 대답에서도 언급했습니다).하지만 초보자로서 실제 코드와 필요한 단계를 이해하려는 경우 더 장황한 것이 도움이되고 개선을 위해 리팩토링 할 수 있습니다. 성능 및 코드를 더 선언적으로 만들기
Sami Birnbaum

1
@SamiBirnbaum과 다소 동의합니다. 이것은 같은 특별한 루비 지식을 거의 사용하지 않는 유일한 것입니다 Hash.new(0). 의사 코드에 가장 가까운 것. 그것은 가독성에 좋은 일이 될 수 있지만 불필요한 작업을하는 것은 그것을 알아 차리는 독자들에게 가독성을 해칠 수 있습니다. 왜냐하면 더 복잡한 경우에는 그들이 왜 그렇게되었는지 알아 내기 위해 미쳐 버릴 것이라고 생각하는 데 약간의 시간을 소비 할 것이기 때문입니다.
Adamantish

1

이것은 대답 이라기보다는 코멘트에 가깝지만, 코멘트는 그것을 정의하지 않습니다. 이렇게하면 Array = foo적어도 하나의 IRB 구현이 중단됩니다.

C:\Documents and Settings\a.grimm>irb
irb(main):001:0> Array = nil
(irb):1: warning: already initialized constant Array
=> nil
C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3177:in `rl_redisplay': undefined method `new' for nil:NilClass (NoMethodError)
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:3873:in `readline_internal_setup'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4704:in `readline_internal'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/rbreadline.rb:4727:in `readline'
        from C:/Ruby19/lib/ruby/site_ruby/1.9.1/readline.rb:40:in `readline'
        from C:/Ruby19/lib/ruby/1.9.1/irb/input-method.rb:115:in `gets'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:139:in `block (2 levels) in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:271:in `signal_status'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:138:in `block in eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `call'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:189:in `buf_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:103:in `getc'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:205:in `match_io'
        from C:/Ruby19/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:287:in `token'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:263:in `lex'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:234:in `block (2 levels) in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `loop'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:230:in `block in each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `each_top_level_statement'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:153:in `eval_input'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:70:in `block in start'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `catch'
        from C:/Ruby19/lib/ruby/1.9.1/irb.rb:69:in `start'
        from C:/Ruby19/bin/irb:12:in `<main>'

C:\Documents and Settings\a.grimm>

Array클래스 이기 때문 입니다.


1
arr = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

arr.uniq.inject({}) {|a, e| a.merge({e => arr.count(e)})}

경과 시간 0.028 밀리 초

흥미롭게도 stupidgeek의 구현은 다음과 같이 벤치마킹되었습니다.

경과 시간 0.041 밀리 초

그리고이기는 대답 :

경과 시간 0.011 밀리 초

:)

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