88 votes

Ruby travaillant sur les éléments de tableau par groupes de quatre

J'ai un script ruby array où chaque élément nécessite un traitement :

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

Cependant, en raison des limitations de ressources, le script fonctionne de manière optimale si plus de quatre éléments sont traités en même temps.

Maintenant je sais que je peux abandonner la boucle each et utiliser une variable pour compter 4 éléments puis attendre, mais y a-t-il un moyen plus cool en Ruby de le faire ?

189voto

Rilindo Points 655

Vous pouvez énumérer par groupes de 4 pour un tableau :

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

Vous pouvez essayer quelque chose comme

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

    end
    (faites quelque chose pour vérifier si les threads sont terminés, sinon attendez)

2 votes

@Rilindo : C'est brillant ! J'ai modifié deux lignes et j'étais prête à partir. Merci.

2 votes

Ma solution ci-dessous devrait être plus efficace lorsque les tâches prennent un temps variable à être traitées. Cette solution suppose que chaque thread prendra le même temps pour traiter une liste de 4 éléments.

2 votes

Je crois que je viens de retomber amoureux de Ruby :)

21voto

Andrew Kuklewicz Points 4942

Si je comprends bien, vous voulez avoir au maximum 4 threads en cours d'exécution à la fois.

Pour moi, il semble que vous devriez lancer uniquement 4 threads et les faire tous lire à partir d'une file d'attente partagée (partie de la bibliothèque de threads standard) pour traiter les éléments.

Vous pouvez terminer les threads lorsque la file d'attente est vide.

Diviser le tableau en 4 tableaux égaux, et avoir chaque thread traiter 1/4 des éléments suppose que chaque élément est traité en même temps. Si certains prennent plus de temps que d'autres, certains de vos threads finiront plus tôt.

En utilisant une file d'attente, aucun thread ne s'arrête tant que la file d'attente partagée n'est pas vide, je pense donc que c'est une solution plus efficace.

Voici un programme fonctionnel basé sur votre code pour illustrer :

require 'thread'
 
elements = [1,2,3,4,5,6,7,8,9,10]
 
def process(element)
    puts "en train de travailler sur #{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 }

0 votes

Cette solution est assez proche de la perfection pour moi, sauf qu'elle lance une erreur à la fin : ArgumentError: tried to create Proc object without a block Semble qu'elle n'aime pas le while (e = queue.pop(true) rescue nil)

0 votes

Je ne reçois pas cette erreur, j'ai essayé 2 versions de ruby - quelle version utilisez-vous?

0 votes

Version 2.3.1. Je l'exécutais à l'intérieur d'une tâche rake dans Rails, il est donc tout à fait possible qu'il y ait un conflit ailleurs.

14voto

Imran Ahmad Points 4242

Dans rails (pas Ruby) un formulaire plus lisible peut être utilisé in_groups_of

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

résultat:

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

La dernière rangée a seulement 3 éléments car nous avons spécifié false comme deuxième argument dans in_group_of. Si vous voulez nil ou toute autre valeur, vous pouvez remplacer false par cette valeur.

2voto

DataWraith Points 1939

Ne sachant pas si la variante suivante compte comme simplement utiliser une "variable pour compter 4 éléments", ou pourrait être considérée comme cool, mais elle vous donne un tableau en tranches de taille ne dépassant pas 4 éléments :

x = (1..10).to_a
0.step(x.size - 1, 4) do |i|
    # Choisir un
    p x.slice(i, 4)
    p x[i, 4]
end

1voto

kibitzer Points 2015

Oui, mais vous devez effectuer une substitution de méthode. L'approche habituelle consiste à substituer '/' pour Array de la manière suivante:

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

Et une fois que cela est défini, vous pouvez facilement faire :

foo = [1,2,3,4,5,6]
foo / 2
# Le résultat est [[1,2], [3,4], [5,6]]

2 votes

Je pense que remplacer des méthodes sur des classes de base est plutôt dangereux - même si (comme dans ce cas) elles n'étaient pas définies auparavant. Pourquoi est-ce / et non pas %? Et si un autre développeur (ou moi, qui ai implémenté ceci) passe par là dans un an ou deux et veut comprendre le code, en se demandant "qu'est-ce que signifie en fait un Array divisé par un nombre"?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X