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.