haskell - 在 Haskell 中命名没有重复名称的树(带子)结构的最佳方法
问题描述
假设我有以下数据结构。
data Tree = Tree
{ name :: String
, children :: [Tree]
, ...
}
我的目标是能够映射树及其子项的列表,以便我可以唯一地命名每棵树,因此以下代码中的 Map 结构表示使用特定名称的次数,换句话说, Map Name Count
. 因此,如果我有baseName :: SystemTree -> String
基于未列出的属性返回未编号名称的函数,它可以与地图中的数字组合,这样即使重用了 baseName,也不会使用任何名称两次。
nameSystemTrees :: Map String Int -> [Tree] -> (Map String Int, [Tree])
nameSystemTrees nameState trees =
...
我的问题是,在 Haskell 中解决这个问题的最佳方法是什么?可以在这里使用可折叠吗?我注意到有这个Data.Tree
包,但不幸的是我已经有很多自定义代码来构建这些树,所以我认为需要一些工作才能使用该包中的构造函数。
解决方案
Well, you can't use Foldable
(or the related class Traversable
) because these classes are for types of kind * -> *
. That is, a Foldable
instance can only be defined for a type like data Tree a = ...
that is parameterized in another type a
, but your data Tree = ...
is unparameterized.
What you can do is write a function that traverses your tree applying a monadic action to each node, sort of a mapM
tailored to your tree that maps a per-node action across the whole tree:
mapTreeM :: Monad m => (Tree -> m Tree) -> Tree -> m Tree
mapTreeM f = mtm -- @f@ is the per-node action, @mtm@ the whole-tree action
where mtm tree = do
-- apply node action @f@ to root node
tree' <- f tree
-- recurse over children with @mtm@
children' <- mapM mtm (children tree')
-- update the children
return $ tree' { children = children' }
Now, this can apply any monadic action, including a State
-based monadic action that assigns a numbered suffix, with a separate counter for each name
. This is, given:
data Tree = Tree
{ name :: String
, children :: [Tree]
} deriving (Show, Eq)
you can define the node-renamer:
uniquifyNode :: Tree -> State (Map String Int) Tree
uniquifyNode node = do
let nm = name node
-- get current count for this name
n <- gets (Map.findWithDefault 1 nm)
-- store an updated count
modify (Map.insert nm (n+1))
-- return uniquified name
return (node { name = nm ++ show n })
and combine the two to create a tree-renamer:
uniquifyTree :: Tree -> Tree
uniquifyTree t = evalState (mapTreeM uniquifyNode t) Map.empty
and test it on a tree:
t0 :: Tree
t0 = Tree "a" [ Tree "a" []
, Tree "b" [ Tree "a" []
, Tree "b" []
, Tree "c" []
]
, Tree "c" [ Tree "a" [] ]
]
like so:
> uniquifyTree t0
which prints a tree equivalent to:
t1 :: Tree
t1 = Tree "a1" [ Tree "a2" []
, Tree "b1" [ Tree "a3" []
, Tree "b2" []
, Tree "c1" []
]
, Tree "c2" [ Tree "a4" [] ]
]
Note that mapTreeM
is essentially equivalent to your mapTree
, and you can define mapTree
in terms of mapTreeM
using runState
and state
which don't actually do anything except wrap and unwrap data types:
mapTree :: ((a, Tree) -> (a, Tree)) -> (a, Tree) -> (a, Tree)
mapTree f (a, t) = let (t', a') = runState (mapTreeM g t) a in (a', t')
where g t = state (\a -> let (a', t') = f (a, t) in (t', a'))
So, structurally, this isn't much different from what you've already done. You just reinvented the state monad (as (a, Tree) -> (a, Tree)
) and wrote a sort of custom mapM
to traverse the tree without making the monadic action general.
One thing about the explicit monadic version is that you can use it with some other monadic actions. Here are some examples:
> -- replace all names with "foo" (Identity action)
> import Data.Functor.Identity
> runIdentity $ mapTreeM (\(Tree n c) -> Identity (Tree "foo" c)) t0
> -- read the names from a file (IO action)
> import System.IO
> withFile "/usr/share/dict/words" ReadMode $
\h -> mapTreeM (\(Tree n c) -> flip Tree c <$> hGetLine h) t0
> -- get a list of names in order (Writer action)
> import Control.Monad.Writer
> execWriter $ mapTreeM (\t@(Tree n _) -> tell [n] >> return t) t0
Anyway, the full program is:
import Control.Monad.State
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
data Tree = Tree
{ name :: String
, children :: [Tree]
} deriving (Show, Eq)
mapTreeM :: Monad m => (Tree -> m Tree) -> Tree -> m Tree
mapTreeM f = mtm
where mtm tree = do
tree' <- f tree
children' <- mapM mtm (children tree')
return $ tree' { children = children' }
uniquifyNode :: Tree -> State (Map String Int) Tree
uniquifyNode node = do
let nm = name node
n <- gets (Map.findWithDefault 1 nm)
modify (Map.insert nm (n+1))
return (node { name = nm ++ show n })
uniquifyTree :: Tree -> Tree
uniquifyTree t = evalState (mapTreeM uniquifyNode t) Map.empty
t0 :: Tree
t0 = Tree "a" [ Tree "a" []
, Tree "b" [ Tree "a" []
, Tree "b" []
, Tree "c" []
]
, Tree "c" [ Tree "a" [] ]
]
t1 :: Tree
t1 = Tree "a1" [ Tree "a2" []
, Tree "b1" [ Tree "a3" []
, Tree "b2" []
, Tree "c1" []
]
, Tree "c2" [ Tree "a4" [] ]
]
main = print $ uniquifyTree t0 == t1
推荐阅读
- r - 如何在平方协方差矩阵中组织协方差对列表?
- jenkins - JMeter 有没有办法记录最近发送的 500 个请求和响应?
- javascript - 尝试导航到动态路由时出现 NextJS 错误
- json - 如何同步 gsheet 行中的计算。从上到下开始
- nestjs - NestJS - 在拦截器中使用服务(不是全局拦截器)
- python - 我可以将一个 numpy 数组分配给 pandas 1.0.3 中的新列吗
- php - FIND_IN_SET 通用符号?
- sql - 删除反转对 - ms 访问 SQL
- javascript - 如何在元素以角度显示之前显示加载动画
- python - 列表的正则表达式