Cách đếm các phần tử chuỗi giống hệt nhau trong một mảng Ruby


91

Tôi có cái sau Array = ["Jason", "Jason", "Teresa", "Judah", "Michelle", "Judah", "Judah", "Allison"]

Làm cách nào để tạo số lượng cho từng phần tử giống nhau ?

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

hoặc tạo ra một hàm băm Trong đó:

Trong đó: hash = {"Jason" => 2, "Judah" => 3, "Allison" => 1, "Teresa" => 1, "Michelle" => 1}


2
Kể từ Ruby 2.7, bạn có thể sử dụng Enumerable#tally. Thông tin thêm ở đây .
SRack

Câu trả lời:


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}

mang đến cho bạn

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

3
+1 Giống như câu trả lời đã chọn, nhưng tôi thích sử dụng tiêm và không có biến "bên ngoài".

18
Nếu bạn sử dụng each_with_objectthay vì injectbạn không phải trả về ( ;total) tại khối.
mfilej

12
Đối với hậu thế, đây là ý nghĩa của @mfilej:array.each_with_object(Hash.new(0)){|string, hash| hash[string] += 1}
Gon Zifroni

2
Từ Ruby 2.7, bạn chỉ có thể làm: names.tally.
Hallgeir Wilhelmsen

99

Ruby v2.7 + (mới nhất)

Kể từ ruby ​​v2.7.0 (phát hành vào tháng 12 năm 2019), ngôn ngữ cốt lõi hiện bao gồm Enumerable#tally- một phương pháp mới , được thiết kế đặc biệt cho vấn đề này:

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

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

Ruby v2.4 + (hiện được hỗ trợ, nhưng cũ hơn)

Mã sau không thể sử dụng trong ruby ​​tiêu chuẩn khi câu hỏi này được hỏi lần đầu (tháng 2 năm 2011), vì nó sử dụng:

  • Object#itself, đã được thêm vào Ruby v2.2.0 (phát hành tháng 12 năm 2014).
  • Hash#transform_values, đã được thêm vào Ruby v2.4.0 (phát hành tháng 12 năm 2016).

Những bổ sung hiện đại này cho Ruby cho phép triển khai sau:

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 + (không được dùng nữa)

Nếu sử dụng phiên bản ruby ​​cũ hơn, không có quyền truy cập vào Hash#transform_valuesphương pháp được đề cập ở trên , thay vào đó bạn có thể sử dụng phương pháp Array#to_hnày đã được thêm vào Ruby v2.1.0 (phát hành tháng 12 năm 2013):

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

Đối với các phiên bản ruby ​​thậm chí cũ hơn ( <= 2.1), có một số cách để giải quyết vấn đề này, nhưng (theo ý kiến ​​của tôi) không có cách nào rõ ràng là "tốt nhất". Xem các câu trả lời khác cho bài đăng này.


Tôi sắp đăng bài: P. Có sự khác biệt rõ ràng nào giữa việc sử dụng countthay vì size/ lengthkhông?
ice ツ

1
@SagarPandya Không, không có sự khác biệt. Không giống như Array#sizeArray#length, Array#count có thể lấy một đối số hoặc khối tùy chọn; nhưng nếu được sử dụng với cả hai thì việc triển khai nó giống hệt nhau. Cụ thể hơn, cả ba phương thức đều gọi ẩn LONG2NUM(RARRAY_LEN(ary)): count / length
Tom Lord

1
Đây là một ví dụ hay về Ruby thành ngữ. Câu trả lời chính xác.
slhck

1
Tín dụng thêm! Sắp xếp theo số lượng.group_by(&:itself).transform_values(&:count).sort_by{|k, v| v}.reverse
Abram

2
@Abram bạn có thể sort_by{ |k, v| -v}, không reversecần thiết! ;-)
Sony Santos

26

Bây giờ sử dụng Ruby 2.2.0, bạn có thể tận dụng itselfphương pháp này .

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
Đồng ý, nhưng tôi hơi thích tên.group_by (&: chính nó) .map {| k, v | [k, v.count]}. to_h để bạn không cần phải khai báo một đối tượng băm
Andy Day

8
@andrewkday Tiến thêm một bước này, ruby ​​v2.4 đã thêm phương thức: Hash#transform_valuescho phép chúng tôi đơn giản hóa mã của bạn hơn nữa:names.group_by(&:itself).transform_values(&:count)
Tom Lord

Ngoài ra, đây là một điểm rất tinh tế (có thể không còn phù hợp với người đọc trong tương lai!), Nhưng lưu ý rằng mã của bạn cũng sử dụng Array#to_h- đã được thêm vào Ruby v2.1.0 (phát hành tháng 12 năm 2013 - tức là gần 3 năm sau câu hỏi ban đầu đã được hỏi!)
Tom Lord

17

Có thực sự là một cấu trúc dữ liệu mà thực hiện điều này: MultiSet.

Thật không may, không có MultiSettriển khai nào trong thư viện lõi Ruby hoặc thư viện chuẩn, nhưng có một số triển khai trôi nổi trên web.

Đây là một ví dụ tuyệt vời về cách lựa chọn cấu trúc dữ liệu có thể đơn giản hóa một thuật toán. Trên thực tế, trong ví dụ cụ thể này, thuật toán thậm chí hoàn toàn biến mất. Nó thực sự chỉ là:

Multiset.new(*names)

Và đó là nó. Ví dụ, sử dụng 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

Ví dụ, sử dụng 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'>

Khái niệm MultiSet có nguồn gốc từ toán học hay một ngôn ngữ lập trình khác?
Andrew Grimm

2
@Andrew Grimm: Cả từ "multiset" (de Bruijn, 1970) và khái niệm (Dedekind 1888) đều bắt nguồn từ toán học. Multisetđược điều chỉnh bởi các quy tắc toán học nghiêm ngặt và hỗ trợ các phép toán tập hợp điển hình (hợp, giao, bù, ...) theo cách hầu hết phù hợp với các tiên đề, định luật và định lý của lý thuyết tập hợp toán học "bình thường", mặc dù một số luật quan trọng không không giữ khi bạn cố gắng tổng quát hóa chúng thành nhiều tập. Nhưng đó là cách vượt quá sự hiểu biết của tôi về vấn đề này. Tôi sử dụng chúng như một cấu trúc dữ liệu lập trình, không phải như một khái niệm toán học.
Jörg W Mittag

Để mở rộng một chút về điểm đó: "... theo cách gần như nhất quán với các tiên đề ..." : Các tập hợp "bình thường" thường được xác định chính thức bởi một tập hợp các tiên đề (giả định) được gọi là "lý thuyết tập hợp Zermelo-Frankel ". Tuy nhiên, một trong những tiên đề này: tiên đề về tính mở rộng nói rằng một tập hợp được xác định chính xác bởi các thành viên của nó - ví dụ {A, A, B} = {A, B}. Đây rõ ràng là một sự vi phạm định nghĩa của nhiều bộ!
Tom Lord

... Tuy nhiên, không cần đi sâu vào quá nhiều chi tiết (vì đây là một diễn đàn phần mềm, không phải toán học nâng cao!), Người ta có thể định nghĩa chính thức nhiều tập hợp một cách toán học thông qua tiên đề cho tập Crisp, tiên đề Peano và các tiên đề MultiSet cụ thể khác.
Tom Lord

13

Enumberable#each_with_object giúp bạn không phải trả lại hàm băm cuối cùng.

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

Lợi nhuận:

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

Đồng ý, each_with_objectbiến thể có thể đọc được với tôi hơninject
Lev Lukomsky

9

Ruby 2.7+

Ruby 2.7 đang giới thiệu Enumerable#tallycho mục đích chính xác này. Có một bản tóm tắt hay ở đây .

Trong trường hợp sử dụng này:

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

Tài liệu về các tính năng đang được phát hành có ở đây .

Hy vọng điều này sẽ giúp ai đó!


Tin tức tuyệt vời!
tadman 19/09/19

6

Những công việc này.

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

2
+1 Đối với một cách tiếp cận khác - mặc dù cách này phức tạp hơn về mặt lý thuyết - O(n^2)(điều này sẽ quan trọng đối với một số giá trị của n) hoạt động thêm (ví dụ: nó phải tính cho "Judah" 3x) !. Tôi cũng xin đề nghị eachthay vì map(kết quả bản đồ đã được bỏ đi)

Cảm ơn vì điều đó! Tôi đã thay đổi bản đồ thành từng mảng. Ngoài ra, tôi đã gỡ bỏ mảng này trước khi xem qua nó. Có lẽ bây giờ vấn đề phức tạp đã được giải quyết?
Shreyas

6

Sau đây là một phong cách lập trình chức năng hơn một chút:

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]]

Một lợi thế của group_bynó là bạn có thể sử dụng nó để nhóm các mục tương đương nhưng không giống hệt nhau:

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]]

Tôi có nghe nói về lập trình chức năng không? +1 :-) Đây chắc chắn là cách tốt nhất, mặc dù có thể lập luận rằng nó không hiệu quả về bộ nhớ. Cũng lưu ý rằng các khía cạnh có tần số # Enumerable.
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}

Tín dụng 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

Rất nhiều triển khai tuyệt vời ở đây.

Nhưng với tư cách là người mới bắt đầu, tôi sẽ coi đây là cách dễ đọc và dễ triển khai nhất

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}

Các bước chúng tôi đã thực hiện:

  • chúng tôi đã tạo băm
  • chúng tôi đã lặp qua namesmảng
  • chúng tôi đã đếm số lần mỗi tên xuất hiện trong namesmảng
  • chúng tôi đã tạo một khóa bằng cách sử dụng namevà một giá trị bằng cách sử dụngcount

Nó có thể dài dòng hơn một chút (và hiệu suất khôn ngoan là bạn sẽ thực hiện một số công việc không cần thiết với các phím ghi đè), nhưng theo ý kiến ​​của tôi, dễ đọc và dễ hiểu hơn cho những gì bạn muốn đạt được


2
Tôi không thấy cách nào dễ đọc hơn câu trả lời được chấp nhận và rõ ràng đó là một thiết kế tồi tệ hơn (thực hiện nhiều công việc không cần thiết).
Tom Lord

@Tom Lord - Tôi đồng ý với bạn về hiệu suất (tôi thậm chí đã đề cập đến điều đó trong câu trả lời của mình) - nhưng là người mới bắt đầu cố gắng hiểu mã thực tế và các bước cần thiết, tôi thấy nó sẽ giúp dài dòng hơn và sau đó người ta có thể cấu trúc lại để cải thiện hiệu suất và làm cho mã khai báo nhiều hơn
Sami Birnbaum

1
Tôi đồng ý phần nào với @SamiBirnbaum. Đây là chiếc duy nhất sử dụng hầu như không có kiến ​​thức đặc biệt về ruby ​​như thế nào Hash.new(0). Gần nhất với mã giả. Điều đó có thể là một điều tốt cho khả năng đọc nhưng cũng có thể làm những công việc không cần thiết có thể gây hại cho khả năng đọc đối với những độc giả chú ý đến nó vì trong những trường hợp phức tạp hơn, họ sẽ dành một chút thời gian để nghĩ rằng họ đang phát điên khi cố gắng tìm ra lý do tại sao nó được hoàn thành.
Adamantish

1

Đây là một bình luận hơn là một câu trả lời, nhưng một bình luận sẽ không công bằng. Nếu bạn làm vậy Array = foo, bạn sẽ gặp sự cố ít nhất một lần triển khai 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>

Đó là bởi vì Arraylà một lớp học.


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

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

Thời gian trôi qua 0,028 mili giây

thật thú vị, việc triển khai của ngu ngốc đã được đánh giá theo tiêu chuẩn:

Thời gian trôi qua 0,041 mili giây

và câu trả lời chiến thắng:

Thời gian trôi qua 0,011 mili giây

:)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.