Это, как я хотел бы написать (я положил все в self-contained gist в случае, если вы хотите играть с ним). Монашка State Int
обрабатывает резьбу счетчика, альтернатива Maybe
выбирает правильное значение. Я думаю, что алгоритм более читабельен таким образом.
Мы начинаем с кучей импорта и ваше определением Tree
:
{-# LANGUAGE GADTs #-}
module NthElement where
import Data.Functor
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.State
data Tree a where
Nil :: Tree a
Node :: Ord a => Tree a -> a -> Tree a -> Tree a
Тогда мы приходим к основному определению: getNthElement
запускает динамическое вычисление, и если мы получили Nothing
назад, он помещает число посетило узлы (исходное число минус окончательное состояние Int
), в противном случае оно возвращает возвращаемое значение.
getNthElement :: Int -> Tree a -> Either Int a
getNthElement k t = maybe (Left $ k - s) Right mv where
(mv , s) = go t `runState` k
go :: Tree a -> State Int (Maybe a)
состояние соединения вычисления сам по себе является прямым переводом вашего высокоуровневого описания алгоритма:
Если мы находим Nil
, нет никакого значения для возврата (так мы не)
go Nil = return Nothing
Если мы находим Node
то энное значение, которое мы ищем либо в правом поддереве, или в середине, если счетчик 0
после изучения правое поддерево, или где-то в левом поддереве:
go (Node l v r) = do
rv <- go r
k <- get
() <- put $ k - 1
lv <- go l
return $ rv <|> (v <$ guard (k == 0)) <|> lv
С этим определением это не сразу ясно, что мы никогда не делаем работу, которая не нужна (мы писать lv <- go l
, когда это может быть случай, когда значение уже было найдено!). Однако ленивости на нашей стороне, и убедить себя в том, что это действительно хорошо, мы можем определить левую бесконечное дерево и наблюдать, что getNthElement
всегда возвращает значение:
leftInfTree :: Tree Int
leftInfTree = foldr (\ k ih -> Node ih k Nil) Nil [1..]
испытание в GHCI:
*NthElement> getNthElement 10 leftInfTree
Right 11
Гвардии действительны только для определения функций, я бы рекомендовал вместо этого использовать выражение case. Охранники - хороший синтаксис для определения функций, случай - это нормальное выражение, которое может быть встроено внутри других выражений. – bheklilr
безупречный! Исправлена. Благодаря! – clay
Должен ли я удалить или подождать, пока кто-нибудь даст ответ, чтобы принять? – clay