【问题标题】:haskell [[Char]] to [[Int]]haskell [[Char]] 到 [[Int]]
【发布时间】:2019-09-30 15:24:29
【问题描述】:

我的 Haskell 程序有问题。 我正在尝试将[[Char]] 更改为[[Int]] 我有

["2","2","1","2,2","1"] 

字符列表列表 我正在尝试将其更改为[[Int]]

[[2],[2],[1],[2,2],[1]]

我试过了

f :: [String] -> [Int]
f = map read

但它给了我

[2,2,1,*** 例外:Prelude.read:无解析

谁能帮我解决这个问题?

【问题讨论】:

  • 这是因为它无法将"2,2" 解析为Int,或者至少不能直接解析。
  • 提示:f 的签名并不完全正确。
  • 你能解释一下或者给我一个提示如何不直接解析它
  • 你可以用逗号作为分隔符来分割它。
  • 我可以想象会出现这样的列表,因为有人在 CSV 文件的内容上调用了 lines。如果您遇到这种情况,那么我强烈建议您使用真正的 CSV 解析器库(例如木薯),以便您正确处理转义等。

标签: list haskell char int


【解决方案1】:

这个失败的原因是因为字符串"2,2"本身不能转换为Int:这是一个数字后跟一个逗号,然后是一个数字。 Int 由一个可选的减号解析,后跟一些数字,以及一些额外的可能性,如十六进制数字,但现在让我们忽略这些。

根据预期的输出,您为 f 指定的类型签名不正确。您的输出类型似乎是Ints 的lists 列表,所以[[Int]]。这意味着您应该将f 指定为:

f :: [String] -> [[Int]]
f = ...

因此,我们需要读取每个String[Int]。我们不能在这里直接使用read,因为reading 到[Int] 期望字符串以方括号 开始和结束。但是,我们可以手动添加这些:

f :: [String] -> [[Int]]
f = map (\s -> read ('[' : s ++ "]"))

无积分版本:

f :: [String] -> [[Int]]
f = map (read . ('[' :) . (++ "]"))

例如:

Prelude> f ["2","2","1","2,2","1"] 
[[2],[2],[1],[2,2],[1]]

使用readMaybe 解析更安全

以上述方式从Strings 解析当然不是很“安全”,因为String 可能不遵循格式。我们可以让它更安全并使用例如readMaybe :: Read a => String -> Maybe a:

import Text.Read(readMaybe)

f :: [String] -> [Maybe [Int]]
f = map (readMaybe . ('[' :) . (++ "]"))

例如:

Prelude Text.Read> f ["2", "3;2", "4,7,3", "bla"]
[Just [2],Nothing,Just [4,7,3],Nothing]

我们可以省略失败的读取,例如使用catMaybes :: [Maybe a] -> [a]:

import Data.Maybe(catMaybes)
import Text.Read(readMaybe)

f :: [String] -> [[Int]]
f = catMaybes . map (readMaybe . ('[' :) . (++ "]"))

例如:

Prelude Data.Maybe Text.Read> f ["2", "3;2", "4,7,3", "bla"]
[[2],[4,7,3]]

或者如@dfeuer所说,如果全部解析成功,我们可以使用traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)返回包裹在Just中的[[Int]]结果,否则Nothing

import Text.Read(readMaybe)

f :: [String] -> Maybe [[Int]]
f = traverse (readMaybe . ('[' :) . (++ "]"))

例如:

Prelude Text.Read> f ["2","2","1","2,2","1"] 
Just [[2],[2],[1],[2,2],[1]]
Prelude Text.Read> f ["2", "3;2", "4,7,3", "bla"]
Nothing

使用readEither 解析错误消息

如果使用readEither :: Read a => String -> Either String a解析失败,我们可以获得包裹在Left中的错误消息:

import Text.Read(readEither)

f :: [String] -> [Either String [Int]]
f = map (readEither . ('[' :) . (++ "]"))

例如:

Prelude Text.Read> f ["2", "3;2", "4,7,3", "bla"]
[Right [2],Left "Prelude.read: no parse",Right [4,7,3],Left "Prelude.read: no parse"]

并以相同的方式使用traverse 来获取包含在Left 中的错误消息或在Right 中的完整结果:

import Text.Read(readEither)

f :: [String] -> Either String [[Int]]
f = traverse (readEither . ('[' :) . (++ "]"))

例如:

Prelude Text.Read> f ["2","2","1","2,2","1"] 
Right [[2],[2],[1],[2,2],[1]]
Prelude Text.Read> f ["2", "3;2", "4,7,3", "bla"]
Left "Prelude.read: no parse"

在这里,就像@dfeuer 所说,它并没有真正显示太多信息。然而,有些解析器可以提供更多信息的解析错误。

【讨论】:

  • 关于catMaybes 的好点。也许你应该添加traverse,我应该删除我原本较差的答案。
  • readEither,遗憾的是,只会产生无用的错误消息。底层解析器缺乏适当的错误处理支持。当您从 Read 升级到真正的解析系统(如 megaparsecattoparsecoptparse-applicativecassava 等)时,此类技术会变得很有用。
猜你喜欢
  • 2011-05-07
  • 1970-01-01
  • 2011-07-10
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
相关资源
最近更新 更多