Как найти все возможные поддеревья двоичного дерева в Haskell?

Мне нужно найти все возможные поддеревья в двоичном дереве:

allSubtrees :: BinaryT a -> [BinaryT a]
allSubtrees = undefined

и дерево это:

data BinaryT a =
    Empty
  | Node (BinaryT a) a (BinaryT a)
  deriving (Eq, Show)

Я новичок в Хаскеле и знаю, что нет while / for Цикл в Хаскеле. Хаскелл это все о рекурсии. У меня вопрос, как получить все возможные поддеревья дерева без бесконечной рекурсии?

5 ответов

bheklilr дал вам ответ на одно из толкований вашего вопроса, но это то, что я бы сказал вам, как новичку, который извлек бы пользу из самостоятельного решения проблемы:

Сначала убедитесь, что вы четко определили, что вы хотите, чтобы ваша функция выполняла. Я предполагаю, что вы хотите, чтобы это работало как tails,

Тогда думай декларативно, где твой = -sign означает "есть", и написать два утверждения. Первый должен читать allSubtrees из Empty дерево это..." (это ваш базовый случай):

allSubtrees Empty = ...

Тогда ваш рекурсивный случай, читая allSubtrees из Node является...":

allSubtrees (Node l a r) = ...something combining the subTrees of l and the subtrees of r

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

Uniplate твой друг, здесь:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Generics.Uniplate.Data (universe)
import Data.Data (Data)
import Data.Typeable (Typeable)

data BinaryT a =
   Empty
   | Node (BinaryT a) a (BinaryT a)
deriving (Eq, Show, Typeable, Data)


allSubtrees :: (Data a, Typeable a) => BinaryT a -> [BinaryT a]
allSubtrees = universe

Так как демонстрация Uniplate уже есть, вот реализация, использующая recursion-schemes ради полноты библиотеки:

{-# LANGUAGE DeriveFunctor, TypeFamilies #-}
import Data.Functor.Foldable

data BinaryT a 
    = Empty
    | Node (BinaryT a) a (BinaryT a)
    deriving (Eq, Show)

data BinaryTBase a b 
    = BaseEmpty 
    | BaseNode b a b
    deriving (Functor)

type instance Base (BinaryT a) = BinaryTBase a

instance Foldable (BinaryT b) where
    project Empty = BaseEmpty
    project (Node a b c) = BaseNode a b c 

instance Unfoldable (BinaryT b) where
    embed BaseEmpty = Empty
    embed (BaseNode a b c) = Node a b c 

allSubtrees :: BinaryT a -> [BinaryT a]     
allSubtrees = para phi where
    phi BaseEmpty = []
    phi (BaseNode (l, ll) v (r, rr)) = ll ++ rr ++ [Node r v l] 

Базовый шаблон базового функтора большой, но относительно неудивительный и может сэкономить ваши усилия в долгосрочной перспективе, так как это один раз для каждого типа.

А вот еще одна реализация, использующая geniplate библиотека:

{-# LANGUAGE TemplateHaskell #-}
import Data.Generics.Geniplate

data BinaryT a =
    Empty
  | Node (BinaryT a) a (BinaryT a)
  deriving (Eq, Show)

allSubTrees :: BinaryT a -> [BinaryT a]
allSubTrees = $(genUniverseBi 'allSubTrees)

А вот сокращенная версия явно рекурсивного подхода @bheklilr, который, вероятно, ожидается от новичка (я использовал (++) для симметрии):

allSubTrees3 :: BinaryT a -> [BinaryT a]
allSubTrees3 Empty = []
allSubTrees3 this @ (Node left _ right) = [this] ++ leftSubs ++ rightSubs where
    leftSubs = allSubTrees3 left
    rightSubs = allSubTrees3 right

Обратите внимание, что он перечисляет корень, но не перечисляет пустые поддеревья, но его легко изменить.

Интересно, каковы преимущества и недостатки разных подходов. Является uniplate как-то более-менее безопасно, чем другие подходы?

Обратите внимание, что recursion-schemes подход является одновременно лаконичным (если вам нужно много разных обходов для одного типа) и гибким (у вас есть полный контроль над порядком обхода, будь то включение пустых поддеревьев и т. д.). Одним из недостатков является тот тип para и другие схемы слишком общие, чтобы разрешить вывод типа, поэтому для устранения неоднозначности часто требуется сигнатура типа.

geniplate кажется менее навязчивым, чем uniplate, так как нет необходимости ставить deriving статьи.

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

allSubTrees :: BinaryT a -> [BinaryT a]
allSubTrees Empty = []
allSubTrees (Node Empty n Empty) = []
allSubTrees (Node Empty n right) = right : allSubTrees right
allSubTrees (Node left n Empty) = left : allSubTrees left
allSubTrees (Node left n right) = left : right : leftSubs ++ rightSubs
    where
        leftSubs = allSubTrees left
        rightSubs = allSubTrees right

В дополнение к решению nponeccop, здесь идет обход дерева в ширину (это невозможно при параморфизме; на самом деле требуется совместная рекурсия):

{-# LANGUAGE DeriveFunctor, TypeFamilies #-}
import Data.Functor.Foldable

data BinaryT a 
    = Empty
    | Node (BinaryT a) a (BinaryT a)
    deriving (Eq, Show)

allSubtrees :: BinaryT a -> [BinaryT a]
allSubtrees t = ana phi [t] where
    phi [] = Nil
    phi (Empty:t) = Cons Empty t
    phi (n@(Node l v r):t) = Cons n (t++(l:[r]))

main = print $ allSubtrees $ 
       Node (Node Empty "a" Empty) "b" (Node (Node Empty "c" Empty) "d" Empty)
Другие вопросы по тегам