功能段落

时间:2009-05-30 21:41:50

标签: haskell functional-programming

抱歉,我还没有完全获得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中完成它?

6 个答案:

答案 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"]]