MapReduce в The Farmer Was Replaced
Обсуждение кода. Часть II.
В предыдущем посте мы обсуждали код, который использует подход MapReduce для выращивания подсолнечника. Мы рассмотрели функцию drone_map_reduce(objects, f_map, f_red) построчно и поняли, что:
👉🏻 objects — массив описаний задач
👉🏻 f_map — фабричная функция, создающая задачу по ее описанию.
👉🏻 f_red — функция редуктора, при необходимости объединяет результаты в один большой результат
Сегодня мы обсудим, как эта структура применяется для решения трех задач по подсолнечнику: обработать все поле, посадить подсолнухи, собрать урожай подсолнухов с бонусом х5.
Обработка поля
def till_field():
columns = []
for x in range(get_world_size()):
columns.append(x)
def till_column(columns):
def g():
for q in columns:
for p in range(get_world_size()):
nav(q, p)
till_soil()
return g
drone_map_reduce(columns, till_column, no_reduce)
Разберем код. Сначала готовим список столбцов для обработки. Затем определяем фабричную функцию till_column, которая принимает список столбцов и возвращает функцию обработки этих столбцов. Внутри используются nav для навигации и till_soil для обработки почвы (обратного переключения почвы здесь нет). Ключевой трюк — вложенная функция g внутри till_column: она замыкает переменную columns и поэтому вызывается без параметров. Именно эту функцию мы передаем в spawn_drone, чтобы задать действия и время жизни дрона.
Посадить подсолнухи
def plant_sunflowers():
drone_jobs = []
for q in range(get_world_size()):
drone_jobs.append(q)
def drone_job(columns):
def g():
heights = dict()
for q in columns:
for p in range(get_world_size()):
nav(q, p)
plant(Entities.Sunflower)
heights[(q, p)] = measure()
return heights
return g
def reduce_hights(h1, h2):
for k in h2:
h1[k] = h2[k]
return h1
return drone_map_reduce(drone_jobs, drone_job, reduce_hights)
Первая часть вполне знакома — формирование списка столбцов для обработки. Функция drone_job снова представляет собой фабрику функций. Опять же, эти функции очень похожи на предыдущие функции till_column. Но теперь у нас есть интересная деталь. У каждого подсолнуха есть свое количество лепестков, и по этим лепесткам мы должны определить, какой подсолнух собирать первым. Итак, после посадки подсолнуха мы вызываем функцию measure() и помещаем ее результат в словарь:
heights[(q, p)] = measure()
Ключами этого словаря являются кортежи координат, а значениями — номера лепестков.
И последняя, но не менее важная деталь: функция reduce_hights. Она принимает два словаря, обновляет первый значениями из второго и возвращает первый словарь. Логика выглядит чуть менее «чистой», но это сделано осознанно. В первой версии я каждый раз создавал новый словарь и сливал в него оба аргумента. На практике это приводило к заметным паузам, потому что цена «чистоты» оказалась квадратичной сложностью. Поэтому вариант с обновлением на месте быстрее.
Я не уверен, что один пост может содержать столько кода, поэтому давайте использовать ссылки на github. Начиная с этой строки, я преобразую словарь из формата (x,y) -> h в h -> [(x1, y1), (x2, y2), ...]. Для каждой высоты подсолнуха теперь у нас есть список ячеек с подсолнухами данной высоты в этих ячейках.
Наконец используем наш подход map-reduce, чтобы собрать все подсолнухи в правильном порядке. gather_all — фабричная функция для этой задачи.
