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

>changeToP (R:ds) (Node x l r) = Node x l (changeToP ds r)

>changeToP [] (Node _ l r) = Node 'P' l r

Если первый элемент в списке направлений – >L, мы строим новое дерево, похожее на прежнее, только элемент в его левом под дереве заменён символом >'P'. Когда мы рекурсивно вызываем функцию >changeToP, то передаём ей только «хвост» списка направлений, потому что мы уже переместились влево. То же самое происходит в случае с направлением >R. Если список направлений пуст, это значит, что мы дошли до нашего места назначения, так что мы возвращаем дерево, аналогичное переданному, за исключением того, что в качестве корневого элемента оно содержит символ >'P'.

Чтобы не распечатывать дерево целиком, давайте создадим функцию, которая принимает список направлений и сообщает нам об элементе в месте назначения:

>elemAt :: Directions –> Tree a –> a

>elemAt (L:ds) (Node _ l _) = elemAt ds l

>elemAt (R:ds) (Node _ _ r) = elemAt ds r

>elemAt [] (Node x _ _) = x

Эта функция на самом деле очень похожа на функцию >changeToP. С одной только разницей: вместо запоминания того, что встречается на пути, и воссоздания дерева она игнорирует всё, кроме своего места назначения. Здесь мы заменяем символ >'W' символом >'P' и проверяем, сохраняется ли изменение в нашем новом дереве:

>ghci> let newTree = changeToP [R,L] freeTree

>ghci> elemAt [R,L] newTree

>'P'

Кажется, работает! В этих функциях список направлений служит чем-то вроде фокуса, потому как в точности указывает на одно поддерево нашего дерева. Например, список направлений >[R] фокусируется на поддереве, находящемся справа от корня. Пустой список направлений фокусируется на самом главном дереве.

Хотя эта техника с виду весьма хороша, она может быть довольно неэффективной, особенно если мы хотим часто изменять элементы. Скажем, у нас есть огромное дерево и длинный список направлений, который указывает весь путь до некоего элемента в самом низу дерева. Мы используем список направлений, чтобы пройтись по дереву и изменить элемент внизу. Если мы хотим изменить другой элемент, который близок к только что изменённому нами элементу, нужно начать с корня дерева и снова пройти весь путь вниз. Какая тоска!..

В следующем разделе мы найдём более удачный способ фокусироваться на поддереве – способ, который позволяет нам эффективно переводить фокус на близлежащие поддеревья.

Тропинка из хлебных крошек

Чтобы фокусироваться на поддереве, нам нужно что-то лучшее, нежели просто список направлений, по которому мы следуем из корня нашего дерева. А могло бы помочь, если бы мы начали с корня дерева и двигались на один шаг влево или вправо за раз, оставляя по пути «хлебные крошки»? Используя этот подход, когда мы идём влево, мы запоминаем, что пошли влево; а когда идём вправо, мы запоминаем, что пошли вправо. Давайте попробуем.