2017-10-05 511 views
3

我想在Haskell中編寫一個程序來按分隔符分割字符串。Haskell - 按分隔符分割字符串

我研究了其他用戶提供的不同示例。下面發佈的代碼就是一個例子。

split :: String -> [String] 
split [] = [""] 
split (c:cs) 
    | c == ',' = "" : rest 
    | otherwise = (c : head rest) : tail rest 
where 
    rest = split cs 

樣本輸入:"1,2,3"。 採樣輸出:["1","2","3"]

我一直在嘗試修改代碼,以便輸出結果類似於["1", "," , "2", "," , "3"],它在輸出中也包含分隔符,但我無法成功。

例如,我改變了行:

| c == ',' = "" : rest 

成:

| c == ',' = "," : rest 

但結果變得["1,","2,","3"]

問題出在哪裏,我誤解了哪部分?

+0

而不是簡單地改變代碼,使其符合您的規範,你可能更好的目的是理解發生了什麼。你能用你自己的話來描述'split'是如何工作的嗎? –

+1

根據我的理解,示例代碼中有兩部分。第一部分split [] = [「」]是我們試圖分割空字符串時返回包含一個空字符串的列表的基本情況。第二部分類似於遞歸,它遍歷輸入中的每個字符。如果該字符是分隔符,則它會將空字符串「」與字符串的剩餘部分的拆分結果組合。但我不確定「|否則=(c:頭部休息):尾部休息」功能。因爲c是字符,但「頭部休息」應該返回一個字符串。 –

+0

您可以簡單地爲此功能執行'split = map pure' – Redu

回答

4

而不是改變代碼,希望它符合預期,通常先理解代碼片段通常會更好。

split :: String -> [String] 
split [] = [""] 
split (c:cs) | c == ',' = "" : rest 
      | otherwise = (c : head rest) : tail rest 
    where rest = split cs 

首先我們更好地分析一下split做什麼。第一條語句簡單地說「空字符串的分割,是一個帶有一個元素的列表,空字符串」。這似乎是合理的。現在,第二個子句指出:「如果字符串的頭部是逗號,我們會生成一個列表,其中第一個元素爲空字符串,然後分割字符串的其餘部分。」「。最後一個警衛說「如果字符串的第一個字符不是逗號,我們會將該字符添加到剩餘字符串拆分的第一個項目,然後是剩餘字符串拆分的其餘元素」。請注意0​​返回一個列表的字符串,所以head rest是一個字符串。

因此,如果我們想要將分隔符添加到輸出中,那麼我們需要在split的輸出中將其添加爲單獨的字符串。哪裏?在第一個後衛。我們不應該返回"," : rest,因爲頭是 - 通過遞歸 - 前置,但是作爲單獨的字符串。所以結果是:

split :: String -> [String] 
split [] = [""] 
split (c:cs) | c == ',' = "" : "," : rest 
      | otherwise = (c : head rest) : tail rest 
    where rest = split cs
3

該示例代碼是不好的風格。除非你確切知道你在做什麼(這些功能是不安全的,partial functions),否則千萬不要使用headtail。而且,平等比較通常更好地寫成專用模式。

考慮到這一點,示例變爲:

split :: String -> [String] 
split "" = [""] 
split (',':cs) = "" : split cs 
split (c:cs) = (c:cellCompletion) : otherCells 
where cellCompletion : otherCells = split cs 

(嚴格地說,這是因爲比賽cellCompletion:otherCells是不可窮盡的仍是不安全的,但它至少在一個明確的地方這將發生如果出現任何問題,請給出明確的錯誤信息。)

現在國際海事組織,這使得它有點更清楚這裏實際發生了什麼:與"" : split cs,打算不是真的要添加一個空單元格的結果。相反,這是添加一個單元格,將由遞歸堆棧進一步調用。發生這種情況是因爲這些調用再次解構了更深的結果,模式匹配cellCompletion : otherCells = split cs,即它們再次彈出第一個單元並預先加載實際單元內容。

因此,如果將其更改爲"," : split,其效果就是您構建的所有單元格都將以,字符預先終止。這不是你想要的。

取而代之,你想添加一個額外的細胞,不會再被觸摸了。這需要在結果則更深:

split (',':cs) = "" : "," : split cs 
1

如果你試圖寫這個功能「真正的」,而不是寫字符一個字符遞歸的實踐,我認爲,一個更清晰的方法是使用Data.Listbreak功能。下面的表達式:

break (==',') str 

符串入一個元組(a,b)其中第一部分由最初的「自由逗號」部分,並且所述第二部分是任一更串開始與逗號或者空,如果有不再有字符串。

這使得定義split清晰簡單:

split str = case break (==',') str of 
       (a, ',':b) -> a : split b 
       (a, "") -> [a] 

您可以驗證此處理split ""(返回[""]),所以沒有必要把它看成一個特例。

這個版本有額外的好處,包括分隔符的修改也很容易理解:

split2 str = case break (==',') str of 
       (a, ',':b) -> a : "," : split2 b 
       (a, "") -> [a] 

請注意,我已經寫了更詳細的這些功能的模式不是要使它絕對清楚發生了什麼,這也意味着Haskell會對每個逗號進行重複檢查。出於這個原因,有些人可能更喜歡:

split str = case break (==',') str of 
       (a, _:b) -> a : split b 
       (a, _) -> [a] 

,或者,如果他們還是想記錄正是他們在每種情況下分支平均預期:

split str = case break (==',') str of 
       (a, _comma:b) -> a : split b 
       (a, _empty) -> [a]