簡體   English   中英

功能段落

[英]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) .nu​​ll (not . null)(/= "")更清晰。 所以我們可以寫

paragraphs = map (filter $ not . null) . groupBy (const $ not . null)

重復(not . null) .nu​​ll (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])

你想對行進行分組,因此groupByData.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM