I put together a skeleton module which should be usable for any of the
problems in this year’s
Google CodeJam Programming Contest (as long as they don’t adopt a new
format/pattern for the problems this year). The module includes some useful
parsing functions which should in the worst case be useful as a cheatsheet for
those not too experienced with
Parsec (*cough me).
I hope it will encourage people to
sign up and use Haskell in
the contest!
To adapt the module to a problem, one defines the following:
a type Case into which we will parse a test case (i.e. problem)
a type SolvedCase, representing a solution to a test case
our algorithm :: Case -> SolvedCase
a Parser caseParser which can parse a single test case (n.b. not the whole file) into the Case type
a function formatCase :: SolvedCase -> String which brings the solution to the state where it can be prepended with the standard “Case #1: ” string
Here is the module, filled in to solve the Minimum Scalar Product practice
problem. You can download the code
here:
moduleMainwhereimportText.ParserCombinators.ParsecimportIOhiding(try)importSystem.Environment-- PROBLEM-SPECIFIC IMPORTS -- importData.List(sort)importControl.Arrow------------------------------ -- the input file will be parsed into a list of some type representing -- individual cases to be solved by our algorithm: typeInput=[Case]-- our algorithms will produce a list of some type SolutionCase: typeSolution=[SolvedCase]-- we convert each solved case into a String, which is zipped with -- the standard "Case #x: " strings for the final output typeOutput=[String]-- --------------- BEGIN EDITING --------------- -- -- DEFINE A TYPE TO REPRESENT A SINGLE UNSOLVED TEST CASE: -- typeCase=-- EXAMPLE: a pair of lists of Ints (our "vectors"): ([Int],[Int])-- DEFINE A TYPE TO REPRESENT A SINGLE SOLVED TEST CASE: -- typeSolvedCase=-- EXAMPLE: our Minimum Scalar Product as an Int: Int-- -- -- ALGORITHMS -- -- -- -- SOLVE A TEST CASE HERE: algorithm::Case->SolvedCasealgorithm=-- EXAMPLE: simply sort both lists, reverse one, and combine: sum.uncurry(zipWith(*)).(reverse.sort***sort)-- -- -- PARSING INPUT -- -- -- -- DEFINE PARSER FOR A TEST CASE: -- caseParser::ParserCasecaseParser=do-- EXAMPLE: parses a case consisting of 3 lines: the first describes the -- number n of elements in the following two lines, the next two lines -- have n space-separated elements: w<-wordletn=readwas<-countnwordbs<-countnwordreturn(mapreadas,mapreadbs)-- -- -- FORMAT OUTPUT -- -- -- -- DEFINE A FUNCTION FROM AN INDIVIDUAL SolvedCase -> String. formatCase::SolvedCase->StringformatCasesol=-- EXAMPLE: nothing to speak of here: showsol-- --------------- STOP EDITING --------------- -- -------------------- -- IO BOILERPLATE -- -------------------- main=do-- pass the input file name to our program: (f:_)<-getArgsfile<-readFilef-- start parsing, solve problem, and prepare output: letinp=parseWithmainParserfilesolution=mapalgorithminpsolutionStrings=mapformatCasesolutionoutp=zipWith(++)prefixessolutionStrings-- write the prepared output to screen: putStr$unlinesoutp-- dies with error, or returns some datatype with our parsed data: parseWithp=either(error.show)id.parsep""-- to begin parsing, we read in a line containing the number of test cases -- to follow. We parse them with caseParser, returning a list: mainParser::ParserInputmainParser=don<-wordms<-count(readn)caseParserreturnms<?>"mainParser"-- strings to prepend to output: prefixes=["Case #"++shown++": "|n<-[1..]]--------------------- -- VARIOUS PARSERS -- --------------------- -- -- LINE PARSERS -- -- -- a single line String, up to the newline: wholeLine::ParserStringwholeLine=manyTillanyChar(trynewline)<?>"wholeLine"-- parse a String with whitespace-separated values into [String] whiteSepLine=manyTillspaceSepWordnewline<?>"whiteSepLine"-- -- WORD PARSERS -- -- -- a single word followed by whitespace (space, newline, etc.): word=dow<-many1nonWhitespacesreturnw<?>"word"-- a single word followed by one or more ' ' characters (won't consume '\n') spaceSepWord=dow<-many1nonWhitemany(char' ')returnw<?>"spaceSepWord"-- e.g. "hello:world" ---> ("hello","world") -- won't consume newlines twoWordsSepByc=dox<-manyTillnonWhite(try$charc)y<-many1nonWhitemany(char' ')return(x,y)<?>"twoWordsSepBy"-- -- CHARACTER PARSERS -- -- -- nonWhitespace character: nonWhite=noneOf" \v\f\t\r\n"<?>"nonWhite"