从Haskell中的IO数据初始化元组
我想知道从Haskell中输入的数据中读取元组的最佳方法是什么。在竞争性编程中,当输入由包含空格分隔整数的几行组成时,我经常遇到这个问题: 要读取整数行,我使用以下函数:从Haskell中的IO数据初始化元组,haskell,lambda,io,tuples,Haskell,Lambda,Io,Tuples,我想知道从Haskell中输入的数据中读取元组的最佳方法是什么。在竞争性编程中,当输入由包含空格分隔整数的几行组成时,我经常遇到这个问题: 要读取整数行,我使用以下函数: readInts::IO[Int] readInts=fmap(map read.words)getLine 然后,我将这些列表转换为具有适当大小的元组: readInts::IO(Int,Int,Int,Int) readInts=fmap(\l->(l!!0,l!!1,l!!2,l!!3)).map read.words
readInts::IO[Int]
readInts=fmap(map read.words)getLine
然后,我将这些列表转换为具有适当大小的元组:
readInts::IO(Int,Int,Int,Int)
readInts=fmap(\l->(l!!0,l!!1,l!!2,l!!3)).map read.words)getLine
这种方法对我来说似乎不太习惯
以下语法更具可读性,但仅适用于2元组:
readInts::IO(Int,Int)
readInts=fmap(\[x,y]->(x,y)).map read.words)getLine
(编辑:如注释中所述,上述解决方案通常适用于n元组)
有没有一种惯用的方法可以从整数列表中初始化元组,而不必使用代码>在Haskell中?或者,是否有不同的方法来处理此类输入?如何:
readInts :: IO (<any tuple you like>)
readInts = read . ("(" ++) . (++ ")") . intercalate "," . words <$> getLine
readInts::IO()
readInts=读取。("(" ++) . (++ ")") . 插入“,”。单词getLine
假定上下文是“竞争性编程”(我只是把它看作一个概念),我不确定下面给出了一个特别有竞争性的替代方案,但是我认为使用几种可用的解析器组合器中的一个是习惯的。
base
软件包附带一个名为Text.parsercompbinators.ReadP
的模块。下面是如何使用它来解析链接文章中的输入文件:
module Q57693986 where
import Text.ParserCombinators.ReadP
parseNumber :: ReadP Integer
parseNumber = read <$> munch1 (`elem` ['0'..'9'])
parseTriple :: ReadP (Integer, Integer, Integer)
parseTriple =
(,,) <$> parseNumber <*> (char ' ' *> parseNumber) <*> (char ' ' *> parseNumber)
parseLine :: ReadS (Integer, Integer, Integer)
parseLine = readP_to_S (parseTriple <* eof)
parseInput :: String -> [(Integer, Integer, Integer)]
parseInput = concatMap (fmap fst . filter (null . snd)) . fmap parseLine . lines
下面是解析该文件的GHCi会话:
*Q57693986> parseInput <$> readFile "57693986.txt"
[(1,3,10),(2,5,8),(10,11,0),(0,0,0)]
元组的第二个元素是仍在等待解析的任何剩余字符串。在上面的示例中,parseLine
已经完全消耗了该行,这是我对格式良好的输入所期望的,因此剩余的String
是空的
如果解析器使用输入的方式不止一种,那么解析器将返回一个备选方案列表,但是,在上面的示例中,由于行已被完全使用,因此只有一个建议的备选方案
parseInput
函数丢弃任何尚未完全使用的元组,然后只拾取任何剩余元组的第一个元素
这种方法经常让我感到困惑,例如,输入文件的格式在哪里比较好。这是一种生成解析器的方法,该解析器一般适用于任何元组(大小合理)。它需要图书馆
它允许不同类型的组件,只要它们都有一个Read
实例
它还解析具有Generics.SOP.Generic
实例的记录:
data Stuff = Stuff { x :: Int, y :: Bool }
deriving (Show,GHC.Generics.Generic,Generics.SOP.Generic)
例如:
*Main> productP @(Int,Int,Int) `readP_to_S` " 1 2 3 "
[((1,2,3)," ")]
*Main> productP @Stuff `readP_to_S` " 1 True"
[(Stuff {x = 1, y = True},"")]
是什么让你认为(\[x,y]->(x,y))
只适用于2元组?事实上,它只适用于更高的arity不管怎样:如果你发现自己在做任何事情时使用了>3个元组,这通常意味着你不应该使用元组,而应该使用合适的记录类型。这个语法的解析错误已经消失了。我可能忘记了一个括号,它误导了我。最后,最惯用的方法是使用真正的解析器。经过一点实践,Haskell解析器非常快速且易于编写。唯一的挑战是在线法官是否提供了必要的库。@Carl“使用真正的解析器”是什么意思?类似于下面luqui的回答?不,我指的是类似于parsec家族的东西(现在可能是megaparsec)。我有时也会使用Earley,但编程竞赛的输入格式通常最容易用上下文敏感的解析器解析,这意味着Earley无法工作。请不要在没有警告或更好的选择的情况下提出这样一个不安全的解决方案。@leftaroundabout我不确定我是否同意您的意见-正如您所指出的,这个解决方案当然是非常不安全的,但在OP的问题(已知输入格式的竞争性编程)的特定情况下,我认为这个解决方案是好的。@bradrn是的,这就是为什么我不说这是一个糟糕的答案。但它仍然是一个潜在的有害因素——人们可能在阅读它时没有考虑竞争性编程环境。我知道有人会反对,因为它的美学非常。。。我喜欢。但是它和OP的例子一样(不)安全,问题不在于安全。你的parseNumber
是不必要的不安全。您应该使用readPrec
解析数字。您可以将所有内容提升到ReadPrec
或将数字降低到ReadP
。当然,像attoparsec或megaparsec这样的东西可能比base中的任何东西都要好。@dfeuer谢谢;我不知道,但下次我会试试的。
{-# LANGUAGE DeriveGeneric, DeriveAnyClass,
FlexibleContexts, TypeFamilies, TypeApplications #-}
import GHC.Generics
import Generics.SOP
import Generics.SOP (hsequence, hcpure,Proxy,to,SOP(SOP),NS(Z),IsProductType,All)
import Data.Char
import Text.ParserCombinators.ReadP
import Text.ParserCombinators.ReadPrec
import Text.Read
componentP :: Read a => ReadP a
componentP = munch isSpace *> readPrec_to_P readPrec 1
productP :: (IsProductType a xs, All Read xs) => ReadP a
productP =
let parserOutside = hsequence (hcpure (Proxy @Read) componentP)
in Generics.SOP.to . SOP . Z <$> parserOutside
*Main> productP @(Int,Int,Int) `readP_to_S` " 1 2 3 "
[((1,2,3)," ")]
data Stuff = Stuff { x :: Int, y :: Bool }
deriving (Show,GHC.Generics.Generic,Generics.SOP.Generic)
*Main> productP @Stuff `readP_to_S` " 1 True"
[(Stuff {x = 1, y = True},"")]