ruby, що працює над елементами масиву групами по чотири


80

У мене є масив рубінових скриптів, коли кожен елемент потребує обробки:

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

однак через обмеження ресурсів сценарій працює оптимально, якщо одночасно не обробляються більше чотири елементи.

ні, я знаю, що можу скинути кожен цикл і використати змінну для підрахунку 4 елементів, а потім почекати, але чи є кулірний рубіновий спосіб це зробити?

Відповіді:


168

Ви можете перерахувати групи по 4 для масиву:

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

Тож ви можете спробувати щось на зразок

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

Це може бути не те, що вам потрібно, однак - я піднявся з 3 ранку, і я проспав лише пару годин. : /


2
@Rilindo: це чудово! змінив два рядки, і мені було добре піти. Дякую.
Елі

2
(моє) наведене нижче рішення має бути більш ефективним, коли для обробки завдань потрібен змінний час. Це рішення передбачає, що для обробки списку з 4 елементів кожен потік займе однаковий час.
Ендрю Куклевич

2
Я думаю, що я просто полюбив Рубі знову і знову :)
надсвітлене

Якщо ви використовуєте Rails, є ще більш читабельний "in_groups_of" ... elements.in_groups_of (4) do | group | бла кінець
Джейсон

21

Якщо я правильно вас прочитав, ви хочете мати не більше 4 потоків обробки одночасно.

Мені здається, що ви повинні запускати лише 4 потоки, і всі вони повинні читати із спільної черги (частина стандартної бібліотеки потоків) для обробки елементів.

Потоки можуть закінчуватися, коли черга порожня.

Нарізання масиву на 4 рівні масиви та обробка кожного потоку 1/4 елементів передбачає, що кожен елемент обробляє одночасно. Якщо деякі займають більше часу, ніж інші, деякі з ваших ниток закінчаться рано.

Використовуючи чергу, жоден потік не зупиняється, поки спільна черга не порожня, тому, я думаю, це більш ефективне рішення.

Ось робоча програма на основі вашого коду для демонстрації:

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 }

Це рішення досить близьке до ідеального для мене, за винятком того, що воно видає помилку після завершення: ArgumentError: tried to create Proc object without a blockЗдається, йому не подобаєтьсяwhile (e = queue.pop(true) rescue nil)
SaltedBlowfish

Я не отримую цієї помилки, спробував 2 версії ruby ​​- яку версію ви використовуєте?
Andrew Kuklewicz

Версія 2.3.1. Я запускав це всередині завдання граблі в Rails, тому цілком можливо, що десь виникає конфлікт.
SaltedBlowfish

2

Не впевнений, чи наведений нижче варіант вважається просто використанням «змінної для підрахунку 4 елементів», або його можна вважати крутим, але він дає масив у фрагментах розміром не більше 4 елементів:

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

2

У рейках можна використовувати більш читабельну форму in_groups_of

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

результат:

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

Останній рядок містить лише 3 елементи, оскільки ми вказали false in_group_of. Якщо ви хочете нуль або будь-яке інше значення, ви можете замінити false на це значення.


in_groups_ofє методом рейок, не буде працювати з звичайним рубіном
Субаш

1

Так, але вам потрібно зробити певний метод, який замінює. Звичайний підхід полягає в тому, щоб замінити '/' приблизно Arrayтак:

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

І з цим визначеним ви тепер можете легко:

foo = [1,2,3,4,5,6]
foo / 2
# Result is [[1,2], [3,4], [5,6]]

2
Я думаю, що перевизначення методів у таких базових класах є досить небезпечним - навіть якщо (як у цьому випадку) вони не були визначені раніше. Чому це /і ні %? Що робити, якщо інший розробник (або я, який це впровадив) заходить через рік-два і захоче зрозуміти код, запитуючи "що Arrayнасправді означає поділене на число"?
haslo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.