如何在 Haskell 中解析整数矩阵?
Posted
技术标签:
【中文标题】如何在 Haskell 中解析整数矩阵?【英文标题】:How do I parse a matrix of integers in Haskell? 【发布时间】:2012-01-12 01:24:42 【问题描述】:所以我已经阅读了理论,现在尝试在 Haskell 中解析文件 - 但我没有得到任何结果。这太奇怪了……
这是我的输入文件的外观:
m n
k1, k2...
a11, ...., an
a21,.... a22
...
am1... amn
其中 m,n 只是整数,K = [k1, k2...]
是整数列表,a11..amn
是“矩阵”(列表列表):A=[[a11,...a1n], ... [am1... amn]]
这是我的快速 python 版本:
def parse(filename):
"""
Input of the form:
m n
k1, k2...
a11, ...., an
a21,.... a22
...
am1... amn
"""
f = open(filename)
(m,n) = f.readline().split()
m = int(m)
n = int(n)
K = [int(k) for k in f.readline().split()]
# Matrix - list of lists
A = []
for i in range(m):
row = [float(el) for el in f.readline().split()]
A.append(row)
return (m, n, K, A)
以下是我在 Haskell 中的表现(不是很):
import System.Environment
import Data.List
main = do
(fname:_) <- getArgs
putStrLn fname --since putStrLn goes to IO ()monad we can't just apply it
parsed <- parse fname
putStrLn parsed
parse fname = do
contents <- readFile fname
-- ,,,missing stuff... ??? how can I get first "element" and match on it?
return contents
我对 monads(以及我陷入其中的上下文!)和 do 语句感到困惑。我很想写这样的东西,但我知道这是错误的:
firstLine <- contents.head
(m,n) <- map read (words firstLine)
因为 contents 不是一个列表 - 而是一个单子。
对下一步步骤的任何帮助都会很棒。
所以我只是 discovered 你可以这样做:
liftM lines . readFile
从文件中获取行列表。但是,该示例仍然只转换了整个文件,并且不只使用第一行或第二行...
【问题讨论】:
【参考方案1】:非常简单的版本可能是:
import Control.Monad (liftM)
-- this operates purely on list of strings
-- and also will fail horribly when passed something that doesn't
-- match the pattern
parse_lines :: [String] -> (Int, Int, [Int], [[Int]])
parse_lines (mn_line : ks_line : matrix_lines) = (m, n, ks, matrix)
where [m, n] = read_ints mn_line
ks = read_ints ks_line
matrix = parse_matrix matrix_lines
-- this here is to loop through remaining lines to form a matrix
parse_matrix :: [String] -> [[Int]]
parse_matrix lines = parse_matrix' lines []
where parse_matrix' [] acc = reverse acc
parse_matrix' (l : ls) acc = parse_matrix' ls $ (read_ints l) : acc
-- this here is to give proper signature for read
read_ints :: String -> [Int]
read_ints = map read . words
-- this reads the file contents and lifts the result into IO
parse_file :: FilePath -> IO (Int, Int, [Int], [[Int]])
parse_file filename = do
file_lines <- (liftM lines . readFile) filename
return $ parse_lines file_lines
您可能希望使用 Parsec 进行更高级的解析,以及更好的错误处理。
*Main Control.Monad> parse_file "test.txt"
(3,3,[1,2,3],[[1,2,3],[4,5,6],[7,8,9]])
【讨论】:
我建议将readFile
移出parse
并改为使用String
。纯函数更容易处理、测试等。
@hammar:是的,这是个好主意。我想我不应该在早上 8 点尝试写 Haskell。
很好:(mn_line:ks_line:matrix_lines)!完全忘记了你可以这样做:-(
另外我不知道你可以匹配这样的列表:[m,n]= stuff
这样 liftM 行。你正在做的 readFile - 它是只提升线条......还是提升整个作品?【参考方案2】:
一个易于编写的解决方案
import Control.Monad (replicateM)
-- Read space seperated words on a line from stdin
readMany :: Read a => IO [a]
readMany = fmap (map read . words) getLine
parse :: IO (Int, Int, [Int], [[Int]])
parse = do
[m, n] <- readMany
ks <- readMany
xss <- replicateM m readMany
return (m, n, ks, xss)
让我们试试吧:
*Main> parse
2 2
123 321
1 2
3 4
(2,2,[123,321],[[1,2],[3,4]])
虽然我提供的代码很有表现力。也就是说,你可以用很少的代码快速完成工作,它有一些不好的属性。虽然我认为如果你还在学习 haskell 并且还没有开始使用解析器库。这是要走的路。
我的解决方案的两个坏属性:
所有代码都在IO
中,没有任何东西可以单独测试
错误处理非常糟糕,正如您在[m, n]
中看到的模式匹配非常激进。如果我们在输入文件的第一行有 3 个元素会怎样?
【讨论】:
这行看起来很棒:xss <- replicateM m readMany
如果需要,可以非常轻松地调整它以使用 hGetLine
从文件而不是标准输入中读取。【参考方案3】:
liftM
不是魔法!您可能会认为将函数 f
提升到 monad 中提升 确实有些神秘,但实际上它只是定义为:
liftM f x = do
y <- x
return (f y)
我们实际上可以使用liftM
来做你想做的事,即:
[m,n] <- liftM (map read . words . head . lines) (readFile fname)
但你要找的是 let 语句:
parseLine = map read . words
parse fname = do
(x:y:xs) <- liftM lines (readFile fname)
let [m,n] = parseLine x
let ks = parseLine y
let matrix = map parseLine xs
return (m,n,ks,matrix)
如您所见,我们可以使用 let 来表示变量赋值,而不是一元计算。事实上,当我们对 do 符号进行脱糖时,let 语句就是让表达式:
parse fname =
liftM lines (readFile fname) >>= (\(x:y:xs) ->
let [m,n] = parseLine x
ks = parseLine y
matrix = map parseLine xs
in return matrix )
【讨论】:
所以... let 允许我在类似单子的上下文中做类似“非单子”的事情? @drozzy - 呃...基本上,是的。您可能想查看有关let
的最新问题:***.com/questions/8274650/…【参考方案4】:
使用解析库的解决方案
由于您可能会有很多人使用将Int
s 字符串解析为[[Int]]
(map (map read . words) . lines $ contents
) 的代码进行响应,因此我将跳过该内容并介绍其中一个解析库。如果您要为实际工作执行此任务,您可能会使用这样的库来解析 ByteString
(而不是 String
,这意味着您的 IO 将所有内容读入单个字符的链接列表)。
import System.Environment
import Control.Monad
import Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString as B
首先,我导入了 Attoparsec 和 bytestring 库。您可以在 hackage 上查看这些库及其文档,并使用 cabal
工具安装它们。
main = do
(fname:_) <- getArgs
putStrLn fname
parsed <- parseX fname
print parsed
main
基本不变。
parseX :: FilePath -> IO (Int, Int, [Int], [[Int]])
parseX fname = do
bs <- B.readFile fname
let res = parseOnly parseDrozzy bs
-- We spew the error messages right here
either (error . show) return res
parseX
(从 parse 重命名以避免名称冲突)使用字节串库的 readfile,它以连续字节的形式读入打包的文件,而不是读入链表的单元格。解析后,如果解析器返回 Right result
,我会使用一些速记来返回结果,或者如果解析器返回值 Left someErrorMessage
,则打印错误。
-- Helper functions, more basic than you might think, but lets ignore it
sint = skipSpace >> int
int = liftM floor number
parseDrozzy :: Parser (Int, Int, [Int], [[Int]])
parseDrozzy = do
m <- sint
n <- sint
skipSpace
ks <- manyTill sint endOfLine
arr <- count m (count n sint)
return (m,n,ks,arr)
然后真正的工作发生在parseDrozzy
。我们使用上面的帮助程序获得了 m
和 n
Int
值。在大多数 Haskell 解析库中,我们必须显式处理空格 - 所以我跳过 n
之后的换行符以到达我们的 ks
。 ks
只是下一个换行符之前的所有 int 值。现在我们实际上可以使用之前指定的行数和列数来获取我们的数组了。
从技术上讲,最后一位 arr <- count m (count n sint)
不符合您的格式。即使这意味着进入下一行,它也会抓取n
ints。我们可以使用 count m (manyTill sint endOfLine)
复制 Python 的行为(不验证一行中的值的数量),或者我们可以更明确地检查每一行的结尾,如果我们缺少元素,则返回错误。
从列表到矩阵
列表列表不是二维数组——空间和性能特征完全不同。让我们使用 Data.Array.Repa (import Data.Array.Repa
) 将列表打包成一个实数矩阵。这将允许我们有效地访问数组的元素以及对整个矩阵执行操作,可选择将工作分散到所有可用的 CPU 中。
Repa 使用稍微奇怪的语法定义数组的维度。如果您的行和列长度在变量m
和n
中,那么Z :. n :. m
很像C 声明int arr[m][n]
。对于一维示例ks
,我们有:
fromList (Z :. (length ks)) ks
这将我们的类型从 [Int]
更改为 Array DIM1 Int
。
对于我们有的二维数组:
let matrix = fromList (Z :. m :. n) (concat arr)
并将我们的类型从 [[Int]]
更改为 Array DIM2 Int
。
所以你有它。使用面向生产的库将文件格式解析为高效的 Haskell 数据结构。
【讨论】:
酷!这种 Array 类型是否允许我查询第一列或第二行之类的内容? 确实如此。关于该操作的一个问题是here。假设您想要列i
,一般形式是slice arr (Z :. All :. i)
。【参考方案5】:
像这样简单的东西呢?
parse :: String -> (Int, Int, [Int], [[Int]])
parse stuff = (m, n, ks, xss)
where (line1:line2:rest) = lines stuff
readMany = map read . words
(m:n:_) = readMany line1
ks = readMany line2
xss = take m $ map (take n . readMany) rest
main :: IO ()
main = do
stuff <- getContents
let (m, n, ks, xss) = parse stuff
print m
print n
print ks
print xss
【讨论】:
你确定这是正确的吗?(line1:line2:rest) = lines stuff
我以为应该是[line1:line2:rest] = lines stuff
是的,我确定:check this out。注意[1:2:[3]] == [[1,2,3]]
,但是(1:2:[3]) == [1,2,3]
。
@drozzy:[foo]
是 (foo:[])
的糖,[foo, bar]
是 (foo:bar:[])
的糖。模式匹配总是使用括号,尽管使用列表糖你实际上看不到括号,直到它被去糖。以上是关于如何在 Haskell 中解析整数矩阵?的主要内容,如果未能解决你的问题,请参考以下文章