Ruby arbeitet an Array-Elementen in Vierergruppen

79

Ich habe ein Ruby-Skript-Array, wenn jedes Element verarbeitet werden muss:

threads = []
elemets.each do  |element|
    threads.push(Thread.new{process(element)}}
end
threads.each { |aThread|  aThread.join }

Aufgrund von Ressourcenbeschränkungen funktioniert das Skript jedoch optimal, wenn nicht mehr die vier Elemente gleichzeitig verarbeitet werden.

Nein, ich weiß, ich kann jede Schleife ausgeben und eine Variable verwenden, um 4 Elemente zu zählen. Warten Sie dann, aber gibt es eine coolere Ruby-Methode, um dies zu tun?

Eli
quelle

Antworten:

166

Sie können ein Array in 4er-Gruppen auflisten:

>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].each_slice(4) {|a| p a}
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]

Sie können also so etwas ausprobieren

elements.each_slice(4) do | batch |
    batch.each do | element |
        threads.push(Thread.new{process(element)}}

    end
    (do stuff to check to see if the threads are done, otherwise wait )
end

Es ist vielleicht nicht das, was Sie brauchen - ich bin seit 3 ​​Uhr morgens auf und habe nur ein paar Stunden geschlafen. : /

Rilindo
quelle
2
@Rilindo: das ist genial! modifizierte zwei Zeilen und ich war gut zu gehen. Vielen Dank.
Eli
2
Die unten stehende (meine) Lösung sollte effizienter sein, wenn die Bearbeitung von Aufgaben unterschiedlich lange dauert. Bei dieser Lösung wird davon ausgegangen, dass jeder Thread dieselbe Zeit benötigt, um eine Liste mit 4 Elementen zu verarbeiten.
Andrew Kuklewicz
2
Ich glaube, ich habe mich einfach wieder in Ruby verliebt :)
Superluminary
Wenn Sie Rails verwenden, gibt es das noch besser lesbare "in_groups_of" ... elements.in_groups_of (4) do | group | bla Ende
Jason
21

Wenn ich Sie richtig gelesen habe, möchten Sie nicht mehr als 4 Threads gleichzeitig verarbeiten.

Klingt für mich so, als sollten Sie nur 4 Threads starten und alle aus einer gemeinsam genutzten Warteschlange (Teil der Standard-Thread-Bibliothek) lesen lassen, um die Elemente zu verarbeiten.

Sie können die Threads beenden lassen, wenn die Warteschlange leer ist.

Wenn Sie das Array in 4 gleiche Arrays aufteilen und jeder Thread 1/4 der Elemente verarbeitet, wird davon ausgegangen, dass jedes Element zur gleichen Zeit verarbeitet wird. Wenn einige länger dauern als andere, werden einige Ihrer Threads vorzeitig beendet.

Bei Verwendung einer Warteschlange stoppt kein Thread, bis die gemeinsam genutzte Warteschlange leer ist. Ich denke, dies ist eine effizientere Lösung.

Hier ist ein Arbeitsprogramm, das auf Ihrem Code basiert, um zu demonstrieren:

require 'thread'

elements = [1,2,3,4,5,6,7,8,9,10]

def process(element)
    puts "working on #{element}"
    sleep rand * 10
end

queue = Queue.new
elements.each{|e| queue << e }

threads = []
4.times do
    threads << Thread.new do
      while (e = queue.pop(true) rescue nil)
        process(e)
      end
    end
end

threads.each {|t| t.join }
Andrew Kuklewicz
quelle
Diese Lösung ist für mich nahezu perfekt, außer dass sie nach Abschluss einen Fehler ArgumentError: tried to create Proc object without a blockwhile (e = queue.pop(true) rescue nil)
auslöst
Ich verstehe diesen Fehler nicht, habe 2 Versionen von Ruby ausprobiert - welche Version verwenden Sie?
Andrew Kuklewicz
Version 2.3.1. Ich habe es innerhalb einer Rake-Aufgabe in Rails ausgeführt, daher ist es durchaus möglich, dass es anderswo einen Konflikt gibt.
SaltedBlowfish
2

Sie sind sich nicht sicher, ob die folgende Variante nur als "Variable zum Zählen von 4 Elementen" gilt oder als cool angesehen werden kann, aber Sie erhalten ein Array in Slices mit einer Größe von nicht mehr als 4 Elementen:

x = (1..10).to_a
0.step(x.size - 1, 4) do |i|
    # Choose one
    p x.slice(i, 4)
    p x[i, 4]
end
DataWraith
quelle
2

In Schienen kann eine besser lesbare Form verwendet werden in_groups_of

arr= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
arr.in_groups_of(4, false) {|a| p a}

Ergebnis:

[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11]

Die letzte Zeile enthält nur 3 Elemente, da wir false in angegeben haben in_group_of. Wenn Sie null oder einen anderen Wert möchten, können Sie false durch diesen Wert ersetzen.

Imran Ahmad
quelle
in_groups_ofist eine Rails-Methode, funktioniert nicht mit einfachem Rubin
Subash
1

Ja, aber Sie müssen einige Methoden überschreiben. Der übliche Ansatz besteht darin, '/' für Folgendes zu überschreiben Array:

class Array
  def / len
    a = []
    each_with_index do |x,i|
      a << [] if i % len == 0
      a.last << x
    end
    a
  end
end 

Und mit dieser Definition können Sie jetzt ganz einfach Folgendes tun:

foo = [1,2,3,4,5,6]
foo / 2
# Result is [[1,2], [3,4], [5,6]]
Kiebitz
quelle
2
Ich denke, dass das Überschreiben von Methoden für solche Grundklassen ziemlich gefährlich ist - auch wenn sie (wie in diesem Fall) zuvor nicht definiert wurden. Warum ist es /und nicht %? Was ist, wenn ein anderer Entwickler (oder ich, der dies implementiert hat) in ein oder zwei Jahren vorbeikommt und den Code verstehen möchte und fragt: "Was zum Teufel bedeutet eine Arraydurch eine Zahl geteilte Zahl tatsächlich?"
Haslo