从 Haskell 中的 IO 数据进行元组初始化



我想知道从 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},"")]

最新更新