52700.fb2
rangeSize
:: (a, a) -> Int
Первый аргумент у всех этих функций это пара, которая представляет верхнюю и нижнюю грань после-
довательности. Попробуйте догадаться, что делают методы этого класса по типам и именам.
Для двумерного массива индекс будет задаваться парой целых чисел:
import Data.Array
type Coord = (Int, Int)
type HeightMap = Array Coord Int
type SinkMap
= Array Coord Coord
Значение типа HeightMap хранит карту высот, значение типа SinkMap хранит в каждой координате, ту
точку, которая является водостоком для данной точки. Нам необходимо построить функцию:
flow :: HeightMap -> SinkMap
Мы будем решать эту задачу рекурсивно. Представим, что мы знаем водостоки для всех точек кроме
данной. Для каждой точки мы можем узнать в какую сторону из неё стекает вода. При этом водосток для
следующей точки такой же как и для текущей. Если же из данной точки вода никуда не течёт, то она сама
является водостоком. Мы определим эту функцию через комбинатор неподвижной точки fix.:
flow :: HeightMap -> SinkMap
flow arr = fix $ \result -> listArray (bounds arr) $
map (\x -> maybe x (result ! ) $ getSink arr x) $
range $ bounds arr
getSink :: HeightMap -> Coord -> Maybe Coord
Мы ищем решение в виде неподвижной точки функции, которая принимает карту стоков и возвращает
карту стоков. Функция getSink по данной точке на карте вычисляет соседнюю точку, в которую стекает вода.
Эта функция частично определена, поскольку для водостоков нет такой соседней точки, в которую бы утекала
вода. Функция listArray конструирует значение типа Array из списка значений. Первым аргументом она
принимает диапазон значений для индексов. Размеры массива совпадают с размерами карты высот, поэтому
первым аргументом мы передаём bounds arr.
Теперь разберёмся с тем как заполняются значения в список. Сначала мы создаём список координат
исходной карты высот с помощью выражения:
range $ bounds arr
После этого мы по координатам точек находим водостоки, причём сразу для всех точек. Это происходит
в лямбда-функции:
\x -> maybe x (result ! ) $ getSink arr x
Водосборы | 187
Мы принимаем текущую координату и с помощью функции getSink находим соседнюю точку, в которую
убегает вода. Если такой точки нет, то в следующем выражении мы вернём исходную точку, поскольку в этом
случае она и будет водостоком, а если такая соседняя точка всё-таки есть мы спросим результат из будущего.
Мы обратимся к результату (result ! ), посмотрим каким окажется водосток для соседней точки и вернём
это значение. Поскольку за счёт ленивых вычислений значения результирующего массива вычисляются лишь
один раз, после того как мы найдём водосток для данной точки этим результатом смогут воспользоваться
все соседние точки. При этом порядок обращения к значениям из будущих вычислений не играет роли.
Осталось только определить функцию поиска ближайшего стока и функцию разметки.
getSink :: HeightMap -> Coord -> Maybe Coord
getSink arr (x, y)
| null sinks = Nothing
| otherwise
= Just $ snd $ minimum $ map (\i -> (arr! i, i)) sinks
where sinks = filter p [(x+1, y), (x-1, y), (x, y-1), (x, y+1)]