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}
]
Željko Filipin
quelle

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.

Nimrodm
quelle
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.">
vladr
quelle
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 }
Manish Shrivastava
quelle
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.

Kaoru
quelle
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.

Carpela
quelle
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.">
Dan
quelle
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 }
Ana María Martínez Gómez
quelle
8

Ruby-Versionen> = 2.7 haben Enumerable # tally .

z.B:

["a", "b", "c", "b"].tally 

#=> { "a" => 1, "b" => 2, "c" => 1 }
Santhosh
quelle
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}]
Milan Novota
quelle
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
}
Arnold Roa
quelle
0

Einfache Implementierung:

(errors_hash = {}).default = 0
array_of_errors.each { |error| errors_hash[error] += 1 }
Evan Senter
quelle
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 
Metakung Fu
quelle
-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])
M.Shajie Shah
quelle