So zählen Sie doppelte Elemente in einem Ruby-Array


68

Ich habe ein sortiertes Array:

[
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
]

Ich würde gerne so etwas bekommen, aber es muss kein Hash sein:

[
  {:error => 'FATAL <error title="Request timed out.">', :count => 2},
  {:error => 'FATAL <error title="There is insufficient system memory to run this query.">', :count => 1}
]

Antworten:


131

Der folgende Code gibt aus , wonach Sie gefragt haben. Ich überlasse Ihnen die Entscheidung, wie Sie den gesuchten Hash tatsächlich generieren möchten:

# sample array
a=["aa","bb","cc","bb","bb","cc"]

# make the hash default to 0 so that += will work correctly
b = Hash.new(0)

# iterate over the array, counting duplicate entries
a.each do |v|
  b[v] += 1
end

b.each do |k, v|
  puts "#{k} appears #{v} times"
end

Hinweis: Ich habe gerade bemerkt, dass Sie gesagt haben, das Array sei bereits sortiert. Der obige Code erfordert keine Sortierung. Die Verwendung dieser Eigenschaft kann zu schnellerem Code führen.


Ich muss es eigentlich nicht drucken, nur ein Hash hat es geschafft. Vielen Dank!
Željko Filipin

3
Ich weiß, dass ich zu spät bin, aber wow. Hash-Standardeinstellungen. Das ist ein wirklich cooler Trick. Vielen Dank!
Matchu

4
Und wenn Sie das maximale Vorkommen finden möchten (und dies in einer einzelnen Zeile tun möchten): a.inject (Hash.new (0)) {| hash, val | Hash [val] + = 1; Hash} .entries.max_by {| entry | entry.last} .... muss es lieben!
Codecraig

2
Sie sollten Enumerable lernen , um den Prozedurcodierungsstil zu vermeiden.
Phil Pirozhkov

68

Sie können dies sehr prägnant (eine Zeile) tun, indem Sie Folgendes verwenden inject:

a = ['FATAL <error title="Request timed out.">',
      'FATAL <error title="Request timed out.">',
      'FATAL <error title="There is insufficient ...">']

b = a.inject(Hash.new(0)) {|h,i| h[i] += 1; h }

b.to_a.each {|error,count| puts "#{count}: #{error}" }

Wird herstellen:

1: FATAL <error title="There is insufficient ...">
2: FATAL <error title="Request timed out.">

12
Mit Ruby 1.9+ können Sie each_with_objectanstelle von inject: a.each_with_object(Hash.new(0)) { |o, h| h[o] += 1 }.
Andrew Marshall

1
@ Andrew - danke, ich bevorzuge die Benennung von, each_with_objectda sie besser mit anderen ähnlichen Methodennamen auf Ruby- Enumerables übereinstimmt.
Matt Huggins

Beachten Sie, dass dies each_with_objectden Code ein wenig vereinfacht, da der Akkumulator nicht der Rückgabewert des Blocks sein muss.
Der Blechmann

29

Wenn Sie ein Array wie dieses haben:

words = ["aa","bb","cc","bb","bb","cc"]

Wenn Sie doppelte Elemente zählen müssen, ist eine einzeilige Lösung:

result = words.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }

23

Ein anderer Ansatz zu den obigen Antworten unter Verwendung von Enumerable # group_by .

[1, 2, 2, 3, 3, 3, 4].group_by(&:itself).map { |k,v| [k, v.count] }.to_h
# {1=>1, 2=>2, 3=>3, 4=>1}

Das in seine verschiedenen Methodenaufrufe aufteilen:

a = [1, 2, 2, 3, 3, 3, 4]
a = a.group_by(&:itself) # {1=>[1], 2=>[2, 2], 3=>[3, 3, 3], 4=>[4]}
a = a.map { |k,v| [k, v.count] } # [[1, 1], [2, 2], [3, 3], [4, 1]]
a = a.to_h # {1=>1, 2=>2, 3=>3, 4=>1}

Enumerable#group_by wurde in Ruby 1.8.7 hinzugefügt.


2
Ich mag (&:itself), das ist genau die richtige Menge an Clever!
Dan Bechard

Eine einzeilige so elegant wie sie kommen. Dies sollte die akzeptierte Antwort sein!
Zor-El

17

Wie wäre es mit folgendem:

things = [1, 2, 2, 3, 3, 3, 4]
things.uniq.map{|t| [t,things.count(t)]}.to_h

Es fühlt sich sauberer und aussagekräftiger an, was wir tatsächlich versuchen.

Ich vermute, dass es auch bei großen Sammlungen besser funktioniert als bei solchen, die über jeden Wert iterieren.

Benchmark-Leistungstest:

a = (1...1000000).map { rand(100)}
                       user     system      total        real
inject                 7.670000   0.010000   7.680000 (  7.985289)
array count            0.040000   0.000000   0.040000 (  0.036650)
each_with_object       0.210000   0.000000   0.210000 (  0.214731)
group_by               0.220000   0.000000   0.220000 (  0.218581)

Es ist also ziemlich viel schneller.


Nicht things.uniqund things.count(t)iteriert über das Array?
Santhosh

Es ist durchaus möglich, dass es unter der Haube funktioniert, also habe ich das vielleicht falsch beschrieben. In jedem Fall scheint der Leistungsgewinn real zu sein, denke ich ...
Carpela

8

Persönlich würde ich es so machen:

# myprogram.rb
a = ['FATAL <error title="Request timed out.">',
'FATAL <error title="Request timed out.">',
'FATAL <error title="There is insufficient system memory to run this query.">']
puts a

Führen Sie dann das Programm aus und leiten Sie es an uniq -c weiter:

ruby myprogram.rb | uniq -c

Ausgabe:

 2 FATAL <error title="Request timed out.">
 1 FATAL <error title="There is insufficient system memory to run this query.">

8

Von Ruby> = 2.2 können Sie verwenden itself:array.group_by(&:itself).transform_values(&:count)

Mit etwas mehr Details:

array = [
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="Request timed out.">',
  'FATAL <error title="There is insufficient system memory to run this query.">'
];

array.group_by(&:itself).transform_values(&:count)
 => { "FATAL <error title=\"Request timed out.\">"=>2,
      "FATAL <error title=\"There is insufficient system memory to run this query.\">"=>1 }


3
a = [1,1,1,2,2,3]
a.uniq.inject([]){|r, i| r << { :error => i, :count => a.select{ |b| b == i }.size } }
=> [{:count=>3, :error=>1}, {:count=>2, :error=>2}, {:count=>1, :error=>3}]

4
Oh, tu das nicht. Sie wiederholen das gesamte Array für jeden Wert!
Glenn McDonald

Dort oben gibt es gute Lösungen. Ich möchte nur die Existenz des Arrays # count erwähnen: a = [1,1,1,2,2,3]; a.uniq.inject ([]) {| r, i | r << {: error => i ,: count => a.count (i)}}
Herr Ronald

1

Wenn Sie dies häufig verwenden möchten, empfehle ich Folgendes:

# lib/core_extensions/array/duplicates_counter
module CoreExtensions
  module Array
    module DuplicatesCounter
      def count_duplicates
        self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h
      end
    end
  end
end

Laden Sie es mit

Array.include CoreExtensions::Array::DuplicatesCounter

Und dann von überall mit nur verwenden:

the_ar = %w(a a a a a a a  chao chao chao hola hola mundo hola chao cachacho hola)
the_ar.duplicates_counter
{
           "a" => 7,
        "chao" => 4,
        "hola" => 4,
       "mundo" => 1,
    "cachacho" => 1
}

0

Einfache Implementierung:

(errors_hash = {}).default = 0
array_of_errors.each { |error| errors_hash[error] += 1 }

2
Diese erste Zeile könnte klarer geschrieben werden miterrors_hash = Hash.new(0)
dem Tin Man

0

Hier ist das Beispielarray:

a=["aa","bb","cc","bb","bb","cc"]
  1. Wählen Sie alle eindeutigen Schlüssel aus.
  2. Für jeden Schlüssel werden sie in einem Hash akkumuliert, um so etwas zu erhalten: {'bb' => ['bb', 'bb']}
    res = a.uniq.inject ({}) {| accu, uni | accu.merge ({uni => a.select {| i | i == uni}})}
    {"aa" => ["aa"], "bb" => ["bb", "bb", "bb"], "cc" => ["cc", "cc"]}

Jetzt können Sie Dinge tun wie:

res['aa'].size 

-3
def find_most_occurred_item(arr)
    return 'Array has unique elements already' if arr.uniq == arr
    m = arr.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
    m.each do |k, v|
        a = arr.max_by { |v| m[v] }
        if v > a
            puts "#{k} appears #{v} times"
        elsif v == a
            puts "#{k} appears #{v} times"
        end 
    end
end

puts find_most_occurred_item([1, 2, 3,4,4,4,3,3])
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.