【问题标题】:What is the best way to split a string by a delimiter functionally?在功能上按分隔符拆分字符串的最佳方法是什么?
【发布时间】:2010-12-21 21:12:39
【问题描述】:

我尝试在 Haskell 中编写程序,该程序将采用逗号分隔的整数字符串,将其转换为整数列表并将每个数字递增 1。

例如 "1,2,-5,-23,15" -> [2,3,-4,-22,16]

下面是生成的程序

import Data.List

main :: IO ()
main = do
  n <- return 1
  putStrLn . show . map (+1) . map toInt . splitByDelimiter delimiter
    $ getList n

getList :: Int -> String
getList n = foldr (++) [] . intersperse [delimiter] $ replicate n inputStr

delimiter = ','

inputStr = "1,2,-5,-23,15"

splitByDelimiter :: Char -> String -> [String]
splitByDelimiter _ "" = []
splitByDelimiter delimiter list =
  map (takeWhile (/= delimiter) . tail)
    (filter (isPrefixOf [delimiter])
       (tails
           (delimiter : list)))

toInt :: String -> Int
toInt = read

对我来说最困难的部分是函数 splitByDelimiter 的编程,它接受一个字符串并返回字符串列表

"1,2,-5,-23,15" -&gt; ["1","2","-5","-23","15"]

认为它有效,但我对它的编写方式不满意。有很多括号,所以看起来像 Lisp。该算法也有些人为:

  1. 在字符串开头添加分隔符",1,2,-5,-23,15"

  2. 生成所有尾部列表[",1,2,-5,-23,15", "1,2,-5,-23,15", ",2,-5,-23,15", .... ]

  3. 过滤并只留下以分隔符[",1,2,-5,-23,15", ",2,-5,-23,15", .... ]开头的字符串

  4. 删除第一个分隔符并取符号,直到遇到下一个分隔符["1", "2", .... ]

所以问题是:

如何改进功能splitByDelimiter

我可以删除前置和删除分隔符并直接拆分字符串吗?

如何重写函数以减少括号?

可能是我遗漏了什么,并且已经有具有此功能的标准功能?

【问题讨论】:

  • foldr (++) [] 也称为concatputStrLn . show 也称为print。另外,n &lt;- return 1 有点奇怪;你可以只做let n = 1 并避免包装和解开单子。

标签: string haskell split


【解决方案1】:

Data.List.Split.splitOn 不这样做吗?

【讨论】:

  • 虽然这个包不是基本安装(Haskell 平台)的一部分,但我认为它往往会被忽视。
  • 谢谢。它完全符合我的需要。
  • splitOneOf 是一个通常更有用的函数,尤其是当您需要考虑任意空白时。
【解决方案2】:
splitBy delimiter = foldr f [[]] 
            where f c l@(x:xs) | c == delimiter = []:l
                             | otherwise = (c:x):xs

编辑:不是原作者的,但下面是一个更(过度?)冗长且不太灵活的版本(特定于Char/String),以帮助阐明其工作原理。使用上述版本,因为它适用于具有 Eq 实例的任何类型列表。

splitBy :: Char -> String -> [String]
splitBy _ "" = [];
splitBy delimiterChar inputString = foldr f [""] inputString
  where f :: Char -> [String] -> [String]
        f currentChar allStrings@(partialString:handledStrings)
          | currentChar == delimiterChar = "":allStrings -- start a new partial string at the head of the list of all strings
          | otherwise = (currentChar:partialString):handledStrings -- add the current char to the partial string

-- input:       "a,b,c"
-- fold steps:
-- first step:  'c' -> [""] -> ["c"]
-- second step: ',' -> ["c"] -> ["","c"]
-- third step:  'b' -> ["","c"] -> ["b","c"]
-- fourth step: ',' -> ["b","c"] -> ["","b","c"]
-- fifth step:  'a' -> ["","b","c"] -> ["a","b","c"]

【讨论】:

  • 这太棒了;我花了很长时间才理解它是如何工作的,但我喜欢它。
  • 但不适用于空字符串,即它的计算结果为 [""] 而不是 []
  • 我同意@ljedrz - 我花了很长时间才理解,但它很棒!我希望您不介意,但我在您的答案中添加了一个不太灵活但极其冗长的附录,以帮助其他人了解正在发生的事情。
  • 次要的挑剔,但这是我期望 splitOn 函数而不是 splitBy 的功能。对于splitBy,我希望splitBy fn = foldr f [[]] where f c l@(x:xs) = bool ((c:x):xs) ([]:l) $ fn c,当前splitOn c 功能由splitOn c = splitBy (==c) 恢复
【解决方案3】:

这有点小技巧,但见鬼,它确实有效。

yourFunc str = map (+1) $ read ("[" ++ str ++ "]")

这是一个使用unfoldr的非hack版本:

import Data.List
import Control.Arrow(second)

-- break' is like break but removes the
-- delimiter from the rest string
break' d = second (drop 1) . break d

split :: String -> Maybe (String,String)
split [] = Nothing
split xs = Just . break' (==',') $ xs

yourFunc :: String -> [Int]
yourFunc = map ((+1) . read) . unfoldr split

【讨论】:

  • 谢谢。这是一个很好的观点。我喜欢展开器在这里的使用方式。
  • 在我的 ghci 中,您的拆分比 splitOn 快 43ns :)
  • 这个 split 函数的实现与你想象的不同——它不能正确地用逗号分隔字符串——缺少一个 ""。如果您想确保拆分函数是 100% 功能性的,它应该是可逆的,可以通过为分隔字符串的所有排列穿插相同的分隔符来实现,例如。 “a,b,c”。
【解决方案4】:

只是为了好玩,下面是如何使用 Parsec 创建一个简单的解析器:

module Main where

import Control.Applicative hiding (many)
import Text.Parsec
import Text.Parsec.String

line :: Parser [Int]
line = number `sepBy` (char ',' *> spaces)

number = read <$> many digit

一个优点是它可以轻松创建一个灵活的解析器:

*Main Text.Parsec Text.Parsec.Token> :load "/home/mikste/programming/Temp.hs"
[1 of 1] Compiling Main             ( /home/mikste/programming/Temp.hs, interpreted )
Ok, modules loaded: Main.
*Main Text.Parsec Text.Parsec.Token> parse line "" "1, 2, 3"
Right [1,2,3]
*Main Text.Parsec Text.Parsec.Token> parse line "" "10,2703,   5, 3"
Right [10,2703,5,3]
*Main Text.Parsec Text.Parsec.Token> 

【讨论】:

  • 次要,但可以像在 number = read &lt;$&gt; many1 digit 中一样使用 many1,以便像“1,,2”这样的无效输入会导致 Left 值,而不是 Prelude.read 中的异常。
【解决方案5】:

这是 HaskellElephant 对原始问题的回答的应用,略有改动

splitByDelimiter :: 字符 -> 字符串 -> [字符串] splitByDelimiter = 展开器。拆分单 splitSingle :: Char -> String -> Maybe (String,String) splitSingle _ [] = 没有 splitSingle 分隔符 xs = let (ys, zs) = break (== delimiter) xs in 只是(是的,下降1 zs)

函数 splitSingle 通过第一个分隔符将列表拆分为两个子字符串。

例如: "1,2,-5,-23,15" -&gt; Just ("1", "2,-5,-23,15")

【讨论】:

    【解决方案6】:
    splitBy del str = helper del str []   
        where 
            helper _ [] acc = let acc0 = reverse acc in [acc0] 
            helper del (x:xs) acc   
                | x==del    = let acc0 = reverse acc in acc0 : helper del xs []  
                | otherwise = let acc0 = x : acc     in helper del xs acc0 
    

    【讨论】:

      【解决方案7】:

      这段代码运行良好 使用:- 拆分“你的字符串”[] 并将 ',' 替换为任何分隔符

      split [] t = [t]
      split (a:l) t = if a==',' then (t:split l []) else split l (t++[a])
      

      【讨论】:

      • 在右侧附加的重复单例是一种反模式(导致二次行为)。
      【解决方案8】:
      import qualified Text.Regex as RegExp
      
      myRegexSplit :: String -> String -> [String]
      myRegexSplit regExp theString = 
        let result = RegExp.splitRegex (RegExp.mkRegex regExp) theString
        in filter (not . null) result
      
      -- using regex has the advantage of making it easy to use a regular
      -- expression instead of only normal strings as delimiters.
      
      -- the splitRegex function tends to return an array with an empty string
      -- as the last element. So the filter takes it out
      
      -- how to use in ghci to split a sentence
      let timeParts = myRegexSplit " " "I love ponies a lot"
      

      【讨论】:

        【解决方案9】:

        另一个没有进口的:

        splitBy :: Char -> String -> [String]
        splitBy _ [] = []
        splitBy c s  =
          let
            i = (length . takeWhile (/= c)) s
            (as, bs) = splitAt i s
          in as : splitBy c (if bs == [] then [] else tail bs)
        

        【讨论】:

        • 使用length 是一种反模式(破坏惰性),请改用span/break(if bs == [] then [] else tail bs) == drop 1 bs.
        猜你喜欢
        • 2014-02-11
        • 1970-01-01
        • 2018-07-27
        • 2020-07-11
        • 1970-01-01
        • 2014-12-05
        • 1970-01-01
        • 1970-01-01
        • 2019-10-02
        相关资源
        最近更新 更多