抱歉,我还没有完全获得FP,我想将一系列行分成一系列行序列,假设一个空行作为段落,我可以在python中这样做:
def get_paraghraps(lines):
paragraphs = []
paragraph = []
for line in lines:
if line == "": # I know it could also be "if line:"
paragraphs.append(paragraph)
paragraph = []
else:
paragraph.append(line)
return paragraphs
你会如何在Erlang或Haskell中完成它?
答案 0 :(得分:4)
我也在努力学习Haskell。这个问题的解决方案可能是:
paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs rest)
where (p, rest) = span (/= "") (dropWhile (== "") lines)
我正在使用Data.List中的函数。我正在使用的那些已经可以从Prelude获得,但你可以在链接中找到他们的文档。
我们的想法是使用span (/= "")
找到第一段。这将返回段落,以及后面的行。然后我们递归到我称之为rest
的较小的行列表。
在拆分第一段之前,我们使用dropWhile (== "")
删除所有空行。吃分隔段落的空行很重要。我的第一次尝试是这样的:
paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs $ tail rest)
where (p, rest) = span (/= "") lines
但是当我们到达最后一段时失败,因为rest
是空字符串:
*Main> paragraphs ["foo", "bar", "", "hehe", "", "bla", "bla"] [["foo","bar"],["hehe"],["bla","bla"]*** Exception: Prelude.tail: empty list
删除空行解决了这个问题,并且它还使代码将任意数量的空行视为段落分隔符,这是我期望的用户。
答案 1 :(得分:4)
我只是一个开始的Haskell程序员(和我5年前学到的小Haskell),但是首先,我会用累加器(“当前段落”)编写函数的自然翻译。被传递(为了清楚起见,我添加了类型):
type Line = String
type Para = [Line]
-- Takes a list of lines, and returns a list of paragraphs
paragraphs :: [Line] -> [Para]
paragraphs ls = paragraphs2 ls []
-- Helper function: takes a list of lines, and the "current paragraph"
paragraphs2 :: [Line] -> Para -> [Para]
paragraphs2 [] para = [para]
paragraphs2 ("":ls) para = para : (paragraphs2 ls [])
paragraphs2 (l:ls) para = paragraphs2 ls (para++[l])
这有效:
*Main> paragraphs ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["Line 3","Line 4"]]
这是一个解决方案。但是,Haskell的经验表明,几乎总有库函数可以做这样的事情:)一个相关的函数被称为groupBy,它几乎可以工作:
paragraphs3 :: [Line] -> [Para]
paragraphs3 ls = groupBy (\x y -> y /= "") ls
*Main> paragraphs3 ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["","Line 3","Line 4"]]
糟糕。我们真正需要的是“splitBy”和it's not in the libraries,但我们可以自己过滤出坏的:
paragraphs4 :: [Line] -> [Para]
paragraphs4 ls = map (filter (/= "")) (groupBy (\x y -> y /= "") ls)
或者,如果你想变得很酷,你可以摆脱这个争论并以毫无意义的方式去做:
paragraphs5 = map (filter (/= "")) . groupBy (\x y -> y /= "")
我确信还有更短的路。 :-)
修改:ephemient指出(not . null)
比(/= "")
更干净。所以我们可以写
paragraphs = map (filter $ not . null) . groupBy (const $ not . null)
重复的(not . null)
强烈表明我们真的应该把它抽象成一个函数,这就是Data.List.Split module所做的,正如下面的答案所指出的那样。
答案 2 :(得分:4)
最干净的解决方案是使用split包中适当的内容。
您需要先安装,但Data.List.Split.splitWhen null
应该完美地完成这项工作。
答案 3 :(得分:3)
递归思考。
get_paragraphs [] paras para = paras ++ [para]
get_paragraphs ("":ls) paras para = get_paragraphs ls (paras ++ [para]) []
get_paragraphs (l:ls) paras para = get_paragraphs ls paras (para ++ [l])
答案 4 :(得分:3)
您想对这些行进行分组,因此来自groupBy
的{{1}}似乎是一个不错的选择。它使用自定义函数来确定哪些行“相等”,因此可以提供使同一段中的行“相等”的内容。例如:
Data.List
这有一些限制,因为import Data.List( groupBy )
inpara :: String -> String -> Bool
inpara _ "" = False
inpara _ _ = True
paragraphs :: [String] -> [[String]]
paragraphs = groupBy inpara
只能比较两个相邻的行,而更复杂的逻辑不适合inpara
给出的框架。如果更灵活,更基本的解决方案。使用基本递归可以写:
groupBy
paragraphs [] = []
paragraphs as = para : paragraphs (dropWhile null reminder)
where (para, reminder) = span (not . null) as
-- splits list at the first empty line
在提供的函数变为false(第一个空行)的位置拆分列表,span
删除所提供函数为true的前导元素(任何前导空行)。
答案 5 :(得分:0)
迟到总比没有好。
import Data.List.Split (splitOn)
paragraphs :: String -> [[String]]
paragraphs s = filter (not . null) $ map words $ splitOn "\n\n" s
paragraphs "a\nb\n\nc\nd" == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n\nc\nd\n\n\n" == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n \n c\nd\n\n\n" == [["a", "b"], ["c", "d"]]