Изучай Haskell во имя добра! (Липовача) - страница 170

и >B на текущий момент и текущую секцию, чтобы найти новый оптимальный путь по >A и >B. Например, вначале оптимальные пути по >A и >B равны, соответственно, >[] и >[]. Мы проверяем секцию >Section 50 10 30 и решаем, что новый оптимальный путь до >A1 – это >[(B,10),(C,30)]; оптимальный путь до >B1 – это >[(B,10)]. Если посмотреть на этот шаг как на функцию, она принимает пару путей и секцию и возвращает новую пару путей. Тип функции такой: >(Path, Path) –> Section –> (Path, Path). Давайте её реализуем – похоже, она нам пригодится.

Подсказка: функция будет нам полезна, потому что её можно использовать в качестве бинарной функции в левой свёртке; тип любой такой функции должен быть >a>–>>b>–>>a.

>roadStep :: (Path, Path) –> Section –> (Path, Path)

>roadStep (pathA, pathB) (Section a b c) =

>   let timeA = sum $ map snd pathA

>       timeB = sum $ map snd pathB

>       forwardTimeToA = timeA + a

>       crossTimeToA = timeB + b + c

>       forwardTimeToB = timeB + b

>       crossTimeToB = timeA + a + c

>       newPathToA = if forwardTimeToA <= crossTimeToA

>                       then (A,a):pathA

>                       else (C,c):(B,b):pathB

>       newPathToB = if forwardTimeToB <= crossTimeToB

>                       then (B,b):pathB

>                       else (C,c):(A,a):pathA

>   in (newPathToA, newPathToB)

Как это работает? Для начала вычисляем оптимальное время по дороге >A, основываясь на текущем лучшем маршруте; то же самое для >B. Мы выполняем >sum $ map snd pathA, так что если >pathA – это что-то вроде >[(A,100),(C,20)], >timeA станет равным >120.

>forwardTimeToA – это время, которое мы потратили бы, если бы ехали до следующего перекрёстка по >A от предыдущего перекрёстка на >A напрямую. Оно равно лучшему времени по дороге >A плюс длительность по >A текущей секции.

>crossTimeToA – это время, которое мы потратили бы, если бы ехали до следующего перекрёстка на >A по >B, а затем повернули бы на >A. Оно равно лучшему времени по >B плюс длительность >B в текущей секции плюс длительность секции >C.

Таким же образом вычисляем >forwardTimeToB и >crossTimeToB. Теперь, когда мы знаем лучший путь до >A и >B, нам нужно создать новые пути до >A и >B с учетом этой информации. Если выгоднее ехать до >A просто напрямую, мы устанавливаем >newPathToA равным >(A,a): pathA. Подставляем метку >A и длину секции >a к началу текущего оптимального пути. Мы полагаем, что лучший путь до следующего перекрёстка по >A – это путь до предыдущего перекрёстка по >A плюс ещё одна секция по >A. Запомните, >A – это просто метка, в то время как >a имеет тип >Int. Для чего мы подставляем их к началу, вместо того чтобы написать