我正在从如下组织的文本文档中提取一些数据:
- "day 1"
- "Person 1"
- "Bill 1"
- "Person 2"
- "Bill 2"
我可以把它读成一个看起来像这样的元组列表:
[(0,["day 1"]),(1,["Person 1"]),(2,["Bill 1"]),(1,["Person 2"]),(2,["Bill 2"])]
每个元组的第一项表示标题级别,第二项表示与每个标题相关的信息。
我的问题是,如何获得如下所示的项目列表:
[["day 1","Person 1","Bill 1"],["day 1","Person 2","Bill 2"]]
即。每个最深嵌套项的一个列表,包含来自其上方标题的所有信息。 我得到的最接近的是:
f [] = []
f (x:xs) = row:f rest where
leaves = takeWhile (\i -> fst i > fst x) xs
rest = dropWhile (\i -> fst i > fst x) xs
row = concat $ map (\i -> (snd x):[snd i]) leaves
这给了我这个:
[[["day 1"],["Intro 1"],["day 1"],["Bill 1"],["day 1"],["Intro 2"],["day 1"],["Bill 2"]]]
我希望这个解决方案适用于任何级别。
P.S。我是Haskell的新手。我有一种感觉,我可以/应该使用树来存储数据,但我无法绕过它。我也想不出一个更好的头衔。
答案 0 :(得分:5)
你应该使用树来存储数据。我将复制Data.Tree
的用法:
data Tree a = Node a (Forest a) deriving (Show)
type Forest a = [Tree a]
现在我们想要获取您的弱类型元组列表,并将其转换为({略})Tree
String
个Parser
。只要您需要转换弱类型值并在转换为更强类型之前对其进行验证,就可以使用type YourData = [(Int, [String])]
type Parser a = YourData -> Maybe (a, YourData)
:
YourData
a
类型同义词表示您要解析的弱类型。 Parser
类型变量是您从解析中检索的值。我们的Maybe
类型返回Parser
,因为Tree
可能会失败。要查看原因,以下输入与有效[(0, ["val1"]), (2, ["val2"])]
不对应,因为它缺少树的第1级:
Parser
如果Parser
成功,它还会返回未使用的输入,以便后续解析阶段可以使用它。
现在,奇怪的是,上面的StateT s Maybe a
类型与众所周知的monad变换器堆栈完全匹配:
StateT s Maybe a ~ s -> Maybe (a, s)
如果您展开underlying implementation of StateT
:
import Control.Monad.Trans.State.Strict
type Parser a = StateT [(Int, [String])] Maybe a
这意味着我们可以定义:
Monad
如果我们这样做,我们会免费为Applicative
类型获取Alternative
,Parser
和parseElement :: Int -> Parser String
parseElement level = StateT $ \list -> case list of
[] -> Nothing
(level', strs):rest -> case strs of
[str] ->
if (level' == level)
then Just (str, rest)
else Nothing
_ -> Nothing
个实例。这使得定义解析器变得非常容易!
首先,我们必须定义一个使用树的单个节点的原始解析器:
Tree
这是我们必须编写的唯一非常重要的代码段,因为它是完整的,处理以下所有极端情况:
下一部分是事情变得优雅的地方。然后我们可以定义两个相互递归的解析器,一个用于解析Forest
,另一个用于解析import Control.Applicative
parseTree :: Int -> Parser (Tree String)
parseTree level = Node <$> parseElement level <*> parseForest (level + 1)
parseForest :: Int -> Parser (Forest String)
parseForest level = many (parseTree level)
:
Applicative
第一个解析器使用StateT
样式,因为Applicative
免费为我们提供了StateT
个实例。但是,我也可以使用Monad
的{{1}}实例来提供对于命令式程序员更具可读性的代码:
parseTree :: Int -> Parser (Tree String)
parseTree level = do
str <- parseElement level
forest <- parseForest (level + 1)
return $ Node str forest
但是many
函数怎么样?那是做什么的?我们来看看它的类型:
many :: (Alternative f) => f a -> f [a]
它会返回一个值并执行Applicative
,而是反复调用它来返回值列表。当我们根据Parser
定义State
类型时,我们免费获得Alternative
个实例,因此我们可以使用many
函数转换解析单个Tree
的内容{1}}(即parseTree
),用于解析Forest
(即parseForest
)的内容。
要使用我们的Parser
,我们只需重命名现有的StateT
功能,以明确其目的:
runParser :: Parser a - &gt; [(Int,[String])] - &gt;也许是 runParser = evalStateT
然后我们就跑了!
>>> runParser (parseForest 0) [(0,["day 1"]),(1,["Person 1"]),(2,["Bill 1"]),(1,["Person 2"]),(2,["Bill 2"])]
Just [Node "day 1" [Node "Person 1" [Node "Bill 1" []],Node "Person 2" [Node "Bill 2" []]]]
那真是太神奇了!让我们看看如果我们给它一个无效的输入会发生什么:
>>> runParser (parseForest 0) [(0, ["val1"]), (2, ["val2"])]
Just [Node "val1" []]
一部分输入成功!我们实际上可以通过定义与输入结尾匹配的解析器来指定它必须使用整个输入:
eof :: Parser ()
eof = StateT $ \list -> case list of
[] -> Just ((), [])
_ -> Nothing
现在让我们试一试:
>>> runParser (parseForest 0 >> eof) [(0, ["val1"]), (2, ["val2"])]
Nothing
完美!
要回答第二个问题,我们再次使用相互递归函数解决问题:
flattenForest :: Forest a -> [[a]]
flattenForest forest = concatMap flattenTree forest
flattenTree :: Tree a -> [[a]]
flattenTree (Node a forest) = case forest of
[] -> [[a]]
_ -> map (a:) (flattenForest forest)
我们试试吧!
>>> flattenForest [Node "day 1" [Node "Person 1" [Node "Bill 1" []],Node "Person 2" [Node "Bill 2" []]]]
[["day 1","Person 1","Bill 1"],["day 1","Person 2","Bill 2"]]
现在,从技术上讲,我不必使用相互递归的函数。我本可以做一个递归函数。我只关注Tree
中Data.Tree
类型的定义。
所以理论上我可以通过跳过中间Tree
类型并直接解析扁平化结果来进一步缩短代码,但我想你可能想要使用基于Tree
的表示形式其他目的。
关键的主要观点是:
如果你这样做,你将编写完全符合问题的强大而优雅的代码。
以下是包含我所说的所有内容的最终代码:
import Control.Applicative
import Control.Monad.Trans.State.Strict
import Data.Tree
type YourType = [(Int, [String])]
type Parser a = StateT [(Int, [String])] Maybe a
runParser :: Parser a -> [(Int, [String])] -> Maybe a
runParser = evalStateT
parseElement :: Int -> Parser String
parseElement level = StateT $ \list -> case list of
[] -> Nothing
(level', strs):rest -> case strs of
[str] ->
if (level' == level)
then Just (str, rest)
else Nothing
_ -> Nothing
parseTree :: Int -> Parser (Tree String)
parseTree level = Node <$> parseElement level <*> parseForest (level + 1)
parseForest :: Int -> Parser (Forest String)
parseForest level = many (parseTree level)
eof :: Parser ()
eof = StateT $ \list -> case list of
[] -> Just ((), [])
_ -> Nothing
flattenForest :: Forest a -> [[a]]
flattenForest forest = concatMap flattenTree forest
flattenTree :: Tree a -> [[a]]
flattenTree (Node a forest) = case forest of
[] -> [[a]]
_ -> map (a:) (flattenForest forest)
答案 1 :(得分:3)
我好像已经解决了。
group :: [(Integer, [String])] -> [[String]]
group ((n, str):ls) = let
(children, rest) = span (\(m, _) -> m > n) ls
subgroups = map (str ++) $ group children
in if null children then [str] ++ group rest
else subgroups ++ group rest
group [] = []
虽然我没有测试过。
想法是注意递归模式。此函数获取列表的第一个元素(N,S),然后将更高级别的所有条目收集到级别为N的另一个元素,并将其收集到列表“children”中。如果没有孩子,我们处于顶层,S形成输出。如果有一些,则将S附加到所有这些。
至于为什么你的算法不起作用,问题主要在row
。请注意,您没有递归下降。
也可以使用树木。
data Tree a = Node a [Tree a] deriving Show
listToTree :: [(Integer, [String])] -> [Tree [String]]
listToTree ((n, str):ls) = let
(children, rest) = span (\(m, _) -> m > n) ls
subtrees = listToTree children
in Node str subtrees : listToTree rest
listToTree [] = []
treeToList :: [Tree [String]] -> [[String]]
treeToList (Node s ns:ts) = children ++ treeToList ts where
children = if null ns then [s] else map (s++) (treeToList ns)
treeToList [] = []
算法基本相同。上半部分转到第一部分,第二部分转到第二部分。