Книга: Программирование на языке Ruby

13.2.9. Реализация параллельных итераторов

13.2.9. Реализация параллельных итераторов

Предположим, что нужно параллельно обходить несколько объектов, то есть для каждого объекта найти первый элемент, потом второй, потом третий и т.д.

Рассмотрим следующий пример. Пусть compose — имя магического метода, который выполняет композицию итераторов. Допустим еще, что у каждого объекта есть стандартный итератор each и что каждый объект возвращает по одному элементу на каждой итерации.

arr1 = [1, 2, 3, 4]
arr2 = [5, 10, 15, 20]
compose(arr1, arr2) {|a,b| puts "#{а} и #{b}" }
# Должно быть напечатано:
# 1 и 5
# 2 и 10
# 3 и 15
# 4 и 20

Можно было бы, конечно, использовать для этой цели zip. Но если нужно более элегантное решение, при котором все элементы не будут храниться одновременно, то без потоков не обойтись. Такое решение представлено в листинге 13.7.

Листинг 13.7. Параллельные итераторы

def compose(*objects)
 threads = []
 for obj in objects do
  threads << Thread.new(obj) do |myobj|
   me = Thread.current
   me[:queue] = []
   myobj.each {|x| me[:queue].push(x) }
  end
 end
 list = [0]                     # Фиктивное значение, отличное от nil.
 while list.nitems > 0 do       # Еще есть не nil.
  list = []
  for thr in threads
   list << thr[:queue].shift    # Удалить по одному из каждого.
  end
  yield list if list.nitems > 0 # He вызывать yield, если все равны nil.
 end
end
x = [1, 2, 3, 4, 5, 6, 7, 8]
y = "    первыйn    второйn    третийn четвертыйn    пятыйn"
z = %w[a b с d e f]
compose(x, у, z) {|a,b,c| p [a, b, c] }
# Выводится:
# [1, "    первыйn", "a"]
# [2, "    второйn", "b"]
# [3, "    третийn", "c"]
# [4, " четвертыйn", "d"]
# [5, "     пятыйn", "e"]
# [6, nil, "f"]
# [7, nil, nil]
# [8, nil, nil]

Обратите внимание: мы не предполагаем, что все объекты имеют одно и то же число элементов. Если один итератор доходит до конца раньше остальных, то он будет генерировать значения nil до тех пор, пока не закончит работу «самый длинный» итератор.

Конечно, можно написать и более общий метод, который на каждой итерации будет обрабатывать более одного элемента. (В конце концов, не все итераторы возвращают по одному значению за раз.) Можно было бы в первом параметре передавать число значений для каждого итератора.

Можно также пользоваться произвольными итераторами (а не только стандартным each). Их имена можно было бы передавать в виде строк, а вызывать с помощью метода send. Много чего еще можно придумать.

Впрочем, мы полагаем, что приведенного кода достаточно для большинства целей. Вариации на эту тему оставляем читателю в качестве упражнения.

Оглавление книги


Генерация: 1.573. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз