[英]Functional paragraphs
對不起我還沒有得到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中做到這一點?
我也在努力學習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
刪除空行解決了這個問題,它還使代碼將任意數量的空行視為段落分隔符,這是我期望的用戶。
我只是一個開始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”, 它不在庫中 ,但我們可以自己過濾掉壞的:
paragraphs4 :: [Line] -> [Para]
paragraphs4 ls = map (filter (/= "")) (groupBy (\x y -> y /= "") ls)
或者,如果你想變得很酷,你可以擺脫爭論並以毫無意義的方式去做:
paragraphs5 = map (filter (/= "")) . groupBy (\x y -> y /= "")
我相信還有更短的路。 :-)
編輯 : ephemient指出(not . null)
.null (not . null)
比(/= "")
更清晰。 所以我們可以寫
paragraphs = map (filter $ not . null) . groupBy (const $ not . null)
重復(not . null)
.null (not . null)
強烈表明我們真的應該將它抽象成一個函數,這就是Data.List.Split模塊所做的,正如下面的答案所指出的那樣。
最干凈的解決方案是使用拆分包中適當的東西。
你需要先安裝它,但是然后Data.List.Split.splitWhen null
應該完美地完成這項工作。
遞歸思考。
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])
你想對行進行分組,因此groupBy
的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
span
在提供的函數變為false(第一個空行)的點處拆分列表, dropWhile
刪除所提供函數為true的前導元素(任何前導空行)。
遲到總比不到好。
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"]]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.