Haskell:整数解析的优雅解决方案

时间:2016-05-10 19:36:09

标签: string parsing haskell

我想编写一个函数来获取字符串中的第一个整数,如果整数位于字符串的开头,后面跟空格或没有。

例如," 12"和#12; 12个孩子"将是有效的字符串,但" 12kids"," a12"无效。

这是我的功能:

getFirstInteger :: String -> Int
getFirstInteger []                                      = error "No integer"
getFirstInteger str
  | dropWhile (Char.isNumber) str == []                 = read str :: Int
  | Char.isSpace $ head $ dropWhile (Char.isNumber) str = read (takeWhile (Char.isNumber) str) :: Int
  | otherwise                                           = error "No integer found" 

问题是如果字符串实际上是一个整数,head将引发异常,这就是第一个条件存在的原因。这个问题有没有优雅的解决方案?

3 个答案:

答案 0 :(得分:4)

getFirstInteger :: String -> Int
getFirstInteger = read . head . words

words会将String拆分为Strings列表,这些列表最初由空格分隔。 head将取第一个(或error如果原始字符串为空),read将像往常一样解析字符串(如果第一个单词不是&#39,则error ; ta有效Int)。

但是,我更喜欢在空error或无法解析的String上使用import Text.Read (readMaybe) getFirstInteger :: String -> Maybe Int getFirstInteger [] = Nothing getFirstInteger xs = readMaybe . head . words $ xs 的变体,例如

listToMaybe

有人可以使用Data.Maybe中的import Data.Maybe (listToMaybe) import Text.Read (readMaybe) import Control.Monad ((>=>)) getFirstInteger :: String -> Maybe Int getFirstInteger = listToMaybe . words >=> readMaybe 完全无点地写这个,但这可能是一种过度杀伤力:

$(document).on("keyup", "input", function(event) {
    // If enter is pressed then hide keyboard.
    if(event.keyCode == 13) {
        $("input").blur();
    }
});

答案 1 :(得分:3)

如果要在不使用解析器组合库或Text.Read中的任何机制的情况下解析字符串,请查看函数breakspan

span :: (a -> Bool) -> [a] -> ([a], [a])
break :: (a -> Bool) -> [a] -> ([a], [a])

好处是这两个函数不仅返回它们匹配的内容,还返回字符串的其余部分,允许您继续解析。

解决您的问题:

import Data.Char

getFirstInteger :: String -> Maybe Int
getFirstInteger str
   let (digs, rest1) = span isDigit str
       endsok = case rest1 of
                  [] -> True
                  (c:_) -> c == ' '
   in
   if not (null digs) && endsok
     then Just (read digs)
     else Nothing          -- either no digits or doesn't end properly

此版本不允许使用前导减号。下一个版本允许整数的可选前导减号:

getFirstInteger' str =
  let (minus,rest1) = span (=='-') str
      (digs, rest2) = span  isDigit rest1
      endsok = case rest2 of
                 [] -> True
                 (c:_) -> c == ' '
  in
  if length minus <= 1 && not (null digs) && endsok
    then Just (read (minus ++ digs))
    else Nothing

是的 - 这不会在输入错误时尽早终止。我主要将其作为如何将调用链接到spanbreak的示例提供。

答案 2 :(得分:2)

使用reads。例如:

type Unit = String

readUnit :: String -> Maybe (Int, Maybe Unit)
readUnit s = case reads s of               -- the integer is at the beginning of the string and...
    (n, ' ':unit):_ -> Just (n, Just unit) -- is followed by space...
    (n, ""      ):_ -> Just (n, Nothing)   -- or nothing.
    _ -> Nothing

在ghci:

> readUnit "12"
Just (12,Nothing)
> readUnit "12 kids"
Just (12,Just "kids")
> readUnit "12kids"
Nothing
> readUnit "a12"
Nothing

但是,要记住一些小的考虑因素。 read可能不会像您想要的那样限制语法;例如,以下答案可能会让您大吃一惊:

> readUnit "  ((-0x5)) kids"
Just (-5,Just "kids")

您可能还想删除单元中的多余空间;例如,您可以将上面的第一个子句更改为

(n, ' ':unit):_ -> Just (n, Just (dropWhile isSpace unit))

或类似的。作为此主题的最终变体,请注意虽然Read的标准实例永远不会返回包含reads中多个元素的列表,但从技术上讲,某些用户提供的类型可能会这样做。因此,如果您曾使用reads来解析Int以外的类型,您可能要么要求明确的解析,要么在决定做什么之前考虑所有解析;上面的代码假设第一个解析与任何解析一样好。