haskell-notes

ping :: (Ord h, Ord a) => Visited a -> ToVisit a h -> [Path a]

ping visited toVisit

| isEmpty toVisit = []

| otherwise

= pong visited toVisit’ a

where (a, toVisit’) = next toVisit

pong :: (Ord h, Ord a)

=> Visited a -> ToVisit a h -> Tree (Path a, h) -> [Path a]

pong visited toVisit a

| inside a visited

= ping visited toVisit

| otherwise

= getPath a :

ping (insert a visited) (schedule (subForest a) toVisit)

Типы Visited и ToVisit обозначают наборы вершин, которые мы уже посетили и которые только собира-

емся посетить. Не вдаваясь в подробности интерфейса этих типов, давайте присмотримся к функциям ping и

pong с точки зрения функции, которая их будет вызывать, а именно функции findPath. Эта функция ожидает

на входе список. Внутри она обходит список в поисках нужного элемента, поэтому она будет применять со-

поставление с образцом, разбирая список на части. Сначала она запросит сопоставление с пустым списком,

запустится функция ping с пустым множеством посещённых вершин (none) и одним элементом в очереди

вершин (singleton a), которые предстоит посетить. Функция ping проверит не является ли очередь пустой,

очередь содержит один элемент, поэтому она перейдёт к следующему случаю и извлечёт из очереди один

элемент (next), который будет передан в функцию pong. Функция pong проверит нет ли в списке уже посе-

щённых элементов того, который был только что извлечён (inside a visited). Если это окажется так, то

она запросит следующий элемент у функции ping. Если же исходный элемент окажется новым, она добавит

его в список (getPath a : … ) и запланирует обход всех дочерних деревьев данного элемента (schedule

(subForest a) toVisit). При первом заходе исходный элемент окажется новым и функция findPath поймёт,

что список не пустой и остановит вычисление. Она немного передохнёт и примется за следующий случай.

Там она будет извлекать первый элемент списка и сопоставлять его с предикатом. При этом первый элемент

уже вычислен. Мы воспользуемся этим, убедимся в том, что он не является целью и рекурсивно вызовем

функцию find на хвосте списка. Функция findPath запросит следующее значение и так далее.

Наша функция flattenPath не является развёрткой, но очень похожа на неё тем, что позволяет вычислять

результирующий список частично. Например функция length требует полного обхода списка. Мы не можем

использовать её с бесконечными списками. Теперь давайте разберёмся с подчинёнными функциями:

getPath :: Tree (Path a, h) -> Path a

getPath = fst . rootLabel

Функции для множества вершин, которые мы уже посетили:

import qualified Data.Set as S

type Visited a

= S.Set a

none :: Ord a => Visited a

none = S. empty

insert :: Ord a => Tree (Path a, h) -> Visited a -> Visited a

insert = S. insert . pathEnd . getPath

inside :: Ord a => Tree (Path a, h) -> Visited a -> Bool

inside = S. member . pathEnd . getPath

Алгоритм эвристического поиска А* | 279

Функции для очереди тех вершин, что мы только собираемся посетить:

import Data.Maybe

import qualified Data.PriorityQueue.FingerTree as Q

type ToVisit a h = Q.PQueue h (Tree (Path a, h))

priority t = (snd $ rootLabel t, t)

singleton :: Ord h => Tree (Path a, h) -> ToVisit a h

singleton = uncurry Q. singleton . priority

next :: Ord h => ToVisit a h -> (Tree (Path a, h), ToVisit a h)

next = fromJust . Q. minView

isEmpty :: Ord h => ToVisit a h -> Bool

isEmpty = Q. null

schedule :: Ord h => [Tree (Path a, h)] -> ToVisit a h -> ToVisit a h

schedule = Q. union . Q. fromList . fmap priority

Эти функции очень простые, они специализируют более общие функции для типов Set и

PQueue, вы наверняка легко разберётесь с ними, заглянув в документацию к модулям Data.Set и

Data.PriorityQueue.FingerTree.

Осталось только написать функцию, которая будет составлять дерево поиска для алгоритма A*. Она при-

нимает функцию ветвления, а также функцию расстояния до цели и строит по ним дерево поиска:

astarTree :: (Num h, Ord h)

=> (a -> [(a, h)]) -> (a -> h) -> a -> Tree (a, h)

astarTree alts distToGoal s0 = unfoldTree f (s0, 0)

where f (s, h) = ((s, heur h s), next h alts s)

heur h s = h + distToGoal s

next h (a, d) = (a, d + h)

Поиск маршрутов в метро

Теперь давайте посмотрим как наша функция справится с задачей поиска маршрутов в метро:

metroTree :: Station -> Station -> Tree (Station, Double)

metroTree init goal = astarTree distMetroMap (stationDist goal) init

connect :: Station -> Station -> Maybe [Station]

connect a b = search (== b) $ metroTree a b

main = print $ connect (St Red Sirius) (St Green Prizrak)

К примеру найдём маршрут от станции “Дно Болота” до станции “Призрак”:

*Metro> connect (St Orange DnoBolota) (St Green Prizrak)

Just [St Orange DnoBolota, St Orange PlBakha,

St Red PlBakha, St Red Sirius, St Green Sirius,

St Green Zvezda, St Green Til,

St Green TrollevMost, St Green Prizrak]

*Metro> connect (St Red PlShekspira) (St Blue De)

Just [St Red PlShekspira, St Red Rodnik, St Blue Rodnik,

St Blue Krest, St Blue De]

*Metro> connect (St Red PlShekspira) (St Orange De)

Nothing

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

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162