我想知道从 Haskell 输入的数据中获取元组的最佳方法是什么。在竞争性编程中,当输入由包含空格分隔整数的几行组成时,我经常遇到这个问题。下面是一个示例:
1 3 10
2 5 8
10 11 0
0 0 0
要读取整数行,我使用以下函数:
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
鉴于上下文是"竞争性编程"(我只是模糊地意识到这是一个概念(,我不确定以下内容是否提供了特别有竞争力的替代方案,但恕我直言,我认为使用几个可用的解析器组合器之一是习惯性的。
base
包附带一个名为Text.ParserCombinators.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
您可以对此输入文件使用parseInput
:
1 3 10
2 5 8
10 11 0
0 0 0
下面是解析该文件的 GHCi 会话:
*Q57693986> parseInput <$> readFile "57693986.txt"
[(1,3,10),(2,5,8),(10,11,0),(0,0,0)]
每个parseLine
函数都会生成一个与解析器匹配的元组列表;例如:
*Q57693986> parseLine "11 32 923"
[((11,32,923),"")]
元组的第二个元素是仍在等待解析的任何剩余String
。在上面的示例中,parseLine
已经完全消耗了该行,这是我对格式良好的输入的期望,因此剩余的String
为空。
如果分析器可以使用多种方式使用输入,则分析器将返回替代项列表,但同样,在上面的示例中,只有一个建议的替代项,因为该行已完全使用。
parseInput
函数会丢弃尚未完全使用的任何元组,然后仅选取任何剩余元组的第一个元素。
这种方法经常为我提供诸如代码降临之类的难题,其中输入文件往往格式良好。
这是一种生成解析器的方法,该解析器通常适用于任何元组(大小合理(。它需要库泛型 sop 。
{-# 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)," ")]
它允许不同类型的组件,只要它们都具有Read
实例即可。
它还分析具有Generics.SOP.Generic
实例的记录:
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},"")]