Year 2024, Quest 1

This commit is contained in:
Anupam Jain 2024-11-27 19:15:33 +05:30
commit b7a6c8a25a
17 changed files with 2370 additions and 0 deletions

12
.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
bower_components/
node_modules/
.pulp-cache/
output/
output-es/
generated-docs/
.psc-package/
.psc*
.purs*
.psa*
.spago

16
package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "everybody-codes",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee",
"dependencies": {
"bignumber.js": "^9.1.2"
}
}

2
pcc Executable file
View file

@ -0,0 +1,2 @@
# The `pcc` part is required as the first argument is the name of the script
node --max-old-space-size=8192 -e "let main = await import('./output/PCC.Main/index.js'); main.main()" -- pcc "$@"

22
pnpm-lock.yaml Normal file
View file

@ -0,0 +1,22 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
bignumber.js:
specifier: ^9.1.2
version: 9.1.2
packages:
bignumber.js@9.1.2:
resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
snapshots:
bignumber.js@9.1.2: {}

1
quests/year2024/quest1-1 Normal file
View file

@ -0,0 +1 @@
BCBCCCACBCACBBACAAABBCCBAACBBCCACAAACACACCBCABBBCBBBCBCAABBCAACCCBBBBBBAACACCBBCABCCABCAABBBCBCBBCAAACBBCACAACBABCACACBABACABBBBAABBACACBCBBCCCCAAACACAACCACBBCACABAAACABBCCCBCCCABBABBABCBACCBBCACCAACBCBAAAACBABBAABCCABCBCABBBAABAACBACACAACBBCACACABABBBCCCAAABAAAAACACCABAABCCAAAAAACCABBBCACABCBAACCAABBBBCBACAACBBCCABABCBABABBBACCABBABBCBBBCABABCCCBABBABCBBAACCCBBABBAAAAAAAACBCACCCCABCBBCCCCAAACAAABBCCABBBACBBCBCCABACABCBBCBBAACCCCBCCCACAACAAACBCCAACBACCACCACACCCCBBBCCABCBBACCAABBCBCACACBCBCCCCBBAABBABBAABACCBCBABCCBACBBAACCBBACBABACBBCCCABABBBBBAACBBCACCCAAACBCBBBBACBABBABBAAAACBACBAABACCACABBCBAABABBACACAACBBBCBBAAAAABCACCABBACACABBBBACCCBBCCABABBAACABBCBBBAABCCABBCAABBCAABBBBAAAACCBCCABCCAABBACCAACBAACBCCCBBBBCCCACCCAAABCAAAAACCACBAAACAACBCCBBACABCCACABCCBCBCABABCBBACCAAABBCBCBCCCBBCBCBBCACBBCBCAAABACABBAACBCCCACCCCABBAACCBCCCAAABAAAACBCBCCACCACCAABBBCBCBCABCBCCBBCBAACBCBACACCBACBBCCCABACACBAACACBBBABABCAACABCBBACAAAACCACACBCBABBCCBACBCCBCCCBCBCBCCBACACBCABACBCABACCACCBAAAACAACBCAABBC

1
quests/year2024/quest1-2 Normal file
View file

@ -0,0 +1 @@
CACBCCBCxADBxCxBBxDAAxBxBCxADAxDCBBBBBADCDCCAAACBAADDCxCDDBCABxBDAAAACxxCACCDDCBBBBCABxBBxBAABDCAACDCBABxDxxBDBAxCDxCCAABAxBDACCDCACxxBxBBxACDBCBDADDDABAABxCBADDxDCDCCxDDDACAxACxABAAABDxDADCABBABDDBACxADAABBCBxACCBCDBDBCCCAADDDxDAADDxBDCCAABDBADBBCAxCBAABCCCCACAAADDDBDxDACAADDBCDxCADCCCBxCCCBADBxBBBBBxACBCDxAxDDDBCCBCCBDDADBDCDDxCDCDDCCACxDBCAAxDCACBDCABAACxBADDBxACBAAABBxBCCxCDDxCDCCDDBBDACAADCDCxCCBDAxDBABDxAACCACABxDCBBCCBADxACDxxxDBxDDDABCCCBCBAAxABADDADxDCBCADxBCBxDDAxACBDxDBCAxCAAxAxACBCxDDACBCxBCBxBDCADABCDxAADCACBBBBBBADCCACBAABxBDCBBDDxDBACDABADxACAxCCxAxBBDBDCABBxBAABDBCxxDBDDAABDCDCADxDDABABBCCBDCCACBCCCxCACBBDCxABAxCADBBDDBCCCACABCCBADDDDDDxCACCBCxDxCBxCDDCCACxDxBBAACBDABDxDABAxDABBDDABBBxBBBCAABCABACDDBCDABDBADDBABxACAxCAxDABDACCABBADBxCACABABAxxACCDADBDCBACDBAABACBAAxABBCCDxDBABDCBBxDDDAADxDBCBCAADBDADCDBBxxCBCxDACCDBCBCDCCABCBBDACADADADBCACBADxDBAACBABDCDDBCBBCDCBDDDDCBACADCAxCACDAACDACDDxBDBBBDBDCBDDCCCDCCxDACBAAxxDCCDBBBBBABBDCBABACCDADDxADDACCCBAABABxCDxDCxDBCxCDADCCAxAxBAxBBxBACDAADxABCBDBACCxBDACDDDCDCCBCDCxCxBCBDxADACCDCDxDAAxABCDDDDCACCABxDDDBBADCBABCDCCBBCBCBxxDBCBxAxBCCCBCAAABxCCDDBBDBBDCCACBABBDCAxAxACDCABCAxDCDABBDBDxxCxCDAAxACCCAABDABBBxDCACCCCDxACDBDBBAxBDxACAABDACCABCBxBBCACCADBBCCCxCBBCDBBBADxCBACBBCCCAxDxAxBDCADABxxAACCBCxDCBDDCxDBBDCCxAAACCBCxACAADCAADAADDCACxBxDDDBDCCxDBDBAADBBCxBCAxDCCBDCABACBDDCCBBCACxCCxBBCCABBxDAACDDAAACDACBBBAABDBACDxxCDABABADxDCCAADBxADADxDxBABDBAxCCBDBxACBABxACxAxABBADBACCBCBDxADBBACBAxBDCACBBDBDBDBDBBxADBCCBACDCACBxDBCCCAACDBDBDBAAACxDDBADAxDxDCCAxxDDCAABBxBBCCCAxAADxDBABBBBCDBBDBBDCBxBCBxDCxADDACBBCCBAADCBAxDCxxBBDxCBDDDADADADABDCDABxADBDDADCAAABADBBxADDDDDxBBDDCBBCDxCBxxCACCBCDACDDCDCxxDxDCACxCAADCCDAACBAADCxxBABDxCxBABDCADBDADACBAABACxCCBBBBBABDCBABDDACABDCBCDABCDDCBCCCxDDCABCAxCDCBACDCBCDDAAAADCCBDxBACxDDADDDAxCDDCDADAABxxBDCCDBCDxBDABCDDABCBCADACAAADxCBDAACABDADBBAABxBCCABCDxBCADBACDDAxCxxCDCxDADBADACDCAxDBAACxDDBCxBDBADCDDBACCACDDCBAACCCCCAxDCCAACCADBBCDDBCABBDACADAAACBCxCxxDCBDBDDDBDDBBBAACADxBDCACBCAx

1
quests/year2024/quest1-3 Normal file

File diff suppressed because one or more lines are too long

1633
spago.lock Normal file

File diff suppressed because it is too large Load diff

52
spago.yaml Normal file
View file

@ -0,0 +1,52 @@
package:
name: everybody-codes
dependencies:
- aff
- ansi
- arrays
- bignumber
- console
- contravariant
- control
- debug
- effect
- either
- enums
- foldable-traversable
- foreign-object
- free
- functors
- integers
- lazy
- maybe
- newtype
- node-buffer
- node-fs
- node-path
- node-readline
- numbers
- optparse
- ordered-collections
- parsing
- prelude
- psci-support
- safe-coerce
- st
- strings
- stringutils
- tuples
- unsafe-coerce
test:
main: Test.Main
dependencies: []
workspace:
packageSet:
registry: 61.2.0
extraPackages:
bignumber:
git: https://forge.id1.in/purescript/purescript-bignumber
ref: main
tuples-native:
git: https://forge.id1.in/aj/purescript-tuples-native.git
ref: main

10
src/Main.purs Normal file
View file

@ -0,0 +1,10 @@
module Main where
import Control.Alternative (pure)
import Data.Unit (Unit, unit)
import Effect (Effect)
main :: Effect Unit
main = do
pure unit

20
src/PCC/Lib.js Normal file
View file

@ -0,0 +1,20 @@
'use strict';
export function unsafeParseInt10(str) {
return parseInt(str, 10);
}
export function unsafeParseIntBase(str) {
return function(base) {
return parseInt(str, base);
};
}
export function unsafeParseFloat(str) {
return parseFloat(str);
}
export function getInputDirectory() {
return process.env.PCC_INPUT_DIRECTORY || 'quests';
}

264
src/PCC/Lib.purs Normal file
View file

@ -0,0 +1,264 @@
module PCC.Lib where
import Control.Applicative (pure)
import Control.Bind (bind, discard, (=<<))
import Control.Category (identity)
import Control.Monad.ST as ST
import Control.Monad.ST.Ref as STRef
import Data.Array ((!!))
import Data.Array as A
import Data.Array.NonEmpty (NonEmptyArray)
import Data.Array.ST as STA
import Data.Array.ST.Iterator as STAI
import Data.BigNumber (BigNumber, parseBigNumber)
import Data.Boolean (otherwise)
import Data.BooleanAlgebra (not)
import Data.CommutativeRing ((+))
import Data.Either (Either(..), either)
import Data.Eq (class Eq, (==))
import Data.EuclideanRing ((-))
import Data.Foldable (foldl)
import Data.Function (($))
import Data.Functor (void, (<$>))
import Data.Int (round)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.NaturalTransformation (type (~>))
import Data.Number (isNaN)
import Data.Ord ((<=), (>))
import Data.Semigroup ((<>))
import Data.Show (show)
import Data.String as S
import Data.String.CodeUnits (fromCharArray, toCharArray)
import Data.String.Pattern (Pattern)
import Data.Unit (Unit)
import Effect (Effect)
import Node.Encoding (Encoding(..))
import Node.FS.Sync (readTextFile, writeTextFile)
import Unsafe.Coerce (unsafeCoerce)
--------------------------------------------------------------------------------
-- Utilities
-- | Apply a function repeatedly a specified number of times
iterate :: forall a. Int -> (a -> a) -> a -> a
iterate i f a
| i > 0 = iterate (i-1) f (f a)
| otherwise = a
-- | Successively run an array of functions on a value
pipeline :: forall a. a -> Array (a -> a) -> a
pipeline a fs = foldl (\a' f -> f a') a fs
-- | Apply a function until fixpoint
untilStable :: forall a. Eq a => (a -> a) -> a -> a
untilStable f a =
let a' = f a
in if a == a'
then a
else untilStable f a'
-- | Apply a function until a predicate is satisfied
until :: forall a. Eq a => (a -> Boolean) -> (a -> a) -> a -> a
until p f a = if p a then a else until p f (f a)
-- | Apply a function until a Right is returned
untilEither :: forall a b. Eq a => (a -> Either a b) -> a -> b
untilEither p a = either (untilEither p) identity (p a)
-- | Convert an either into a maybe
eitherToMaybe :: forall e a. Either e a -> Maybe a
eitherToMaybe (Left _) = Nothing
eitherToMaybe (Right a) = Just a
--------------------------------------------------------------------------------
-- Bignumber
-- HACKY!
-- | Convert an integer into a bignumber
intToBigNumber :: Int -> BigNumber
intToBigNumber x = case parseBigNumber (show x) of
Left _ -> unsafeCoerce "IMPOSSIBLE: fromInt: INVALID BIGNUM!"
Right y -> y
--------------------------------------------------------------------------------
-- Array Utilities
-- | Split an array into subarrays of equal length
chunksOf :: forall a. Int -> Array a -> Array (Array a)
chunksOf n l = case A.uncons l of
Nothing -> []
Just _ -> A.cons (A.take n l) (chunksOf n (A.drop n l))
-- | Make an array with the specified start point,
-- | specified offset between elements, and the specified number of elements
mkArr :: Int -> Int -> Int -> Array Int
mkArr start offset items = go start items []
where
go curr i arr
| i <= 0 = arr
| otherwise = go (curr+offset) (items-1) (A.cons curr arr)
-- | Returns all final segments of the argument, longest first
tails :: forall a. Array a -> Array (Array a)
tails arr = case A.uncons arr of
Nothing -> A.singleton arr
Just x -> A.cons arr (tails x.tail)
-- | Count the number of elements in an array that match a predicate
countMatching :: forall a. (a -> Boolean) -> Array a -> Int
countMatching p = go 0
where
go n arr = case A.uncons arr of
Nothing -> n
Just x -> if p x.head then go (n+1) x.tail else go n x.tail
-- | Split an array into two parts:
-- |
-- | 1. the longest initial subarray for which all elements return a Just value
-- | for the supplied function, and returns the values mapped to the Just values
-- | 2. the remaining elements
spanMap :: forall a b. (a -> Maybe b) -> Array a -> {init :: Array b, rest :: Array a}
spanMap p arr = go 0 []
where
go i prev = case A.index arr i of
Nothing -> {init: prev, rest: []}
Just x -> case p x of
Nothing ->
{ init: prev
, rest:
if i == 0
then arr
else A.slice i (A.length arr) arr
}
Just b -> go (i + 1) (A.snoc prev b)
-- | Delete all the given indices from the array
-- | Skips over any invalid indices
deleteAll :: forall a. Array Int -> Array a -> Array a
deleteAll indices arr =
foldl (\arr' x -> fromMaybe arr (A.deleteAt x arr')) arr indices
-- | Like Array.groupBy, except it only even compares consecutive elements
-- | As an example where Array.groupBy would not work - this groupBY can be used to group together runs of consecutive numbers
-- | groupBySeq (\a b -> b-a == 1 ) [1,2,3,5,6,9,11,13,14] = [[1,2,3],[5,6],[9],[11],[13,14]]
-- | Most of this implementation was copied verbatim from the functions in `Data.Array`
groupBySeq :: forall a. (a -> a -> Boolean) -> Array a -> Array (NonEmptyArray a)
groupBySeq op xs =
ST.run do
result <- STA.new
iter <- STAI.iterator (xs !! _)
STAI.iterate iter \x -> void do
sub <- STA.new
cmp <- STRef.new x
_ <- STA.push x sub
pushWhile (\z -> do
b <- runOp op cmp z
_ <- STRef.write z cmp
pure b
) iter sub
grp <- STA.unsafeFreeze sub
STA.push ((unsafeCoerce :: Array ~> NonEmptyArray) grp) result
STA.unsafeFreeze result
where
pushWhile :: forall r. (a -> ST.ST r Boolean) -> STAI.Iterator r a -> STA.STArray r a -> ST.ST r Unit
pushWhile p iter array = do
break <- STRef.new false
ST.while (not <$> STRef.read break) do
mx <- STAI.peek iter
case mx of
Just x -> do
b <- p x
if b then do
_ <- STA.push x array
void $ STAI.next iter
else
void $ STRef.write true break
_ ->
void $ STRef.write true break
runOp :: forall r. (a -> a -> Boolean) -> STRef.STRef r a -> a -> ST.ST r Boolean
runOp f r a = do
b <- STRef.read r
pure (f b a)
--------------------------------------------------------------------------------
-- Text splitting
chars :: String -> Array Char
chars = toCharArray
fromChars :: Array Char -> String
fromChars = fromCharArray
-- | Split by a pattern once and return left and right params
-- | To make multiple splits, use `Data.String.split`
splitFirst :: Pattern -> String -> Maybe {left::String, right::String}
splitFirst p s = do
let arr = S.split p s
x <- A.uncons arr
y <- A.uncons x.tail
pure {left:x.head, right: y.head}
-- | Split a string into chunks of fixed length
chunk :: Int -> String -> Array String
chunk len contents
| S.length contents <= 0 = []
| otherwise =
let res = S.splitAt len contents
in A.cons res.before (chunk len res.after)
--------------------------------------------------------------------------------
-- Getting the input
foreign import getInputDirectory :: Effect String
-- | The location of the input file, given a year and quest
inputFileLocationYearQuest :: String -> String -> String -> Effect String
inputFileLocationYearQuest y d p = do
inputDirectory <- getInputDirectory
pure $ inputDirectory <> "/year" <> y <> "/quest" <> d <> "-" <> p
-- | The location of the test file, given a year and quest
testFileLocationYearQuest :: String -> String -> Effect String
testFileLocationYearQuest y d = do
inputDirectory <- getInputDirectory
pure $ inputDirectory <> "/year" <> y <> "/test" <> d
-- | Read the entire input file into a single string, given a year and quest
readInputYearQuest :: String -> String -> String -> Effect String
readInputYearQuest year quest part = readTextFile UTF8 =<< inputFileLocationYearQuest year quest part
-- | Write the input file, given a year and quest
writeInputYearQuest :: String -> String -> String -> String -> Effect Unit
writeInputYearQuest year quest part contents = do
loc <- inputFileLocationYearQuest year quest part
writeTextFile UTF8 loc contents
--------------------------------------------------------------------------------
-- Parsing
-- These return `Number` instead of `Int` because they can return `NaN`
foreign import unsafeParseIntBase :: String -> Int -> Number
foreign import unsafeParseInt10 :: String -> Number
foreign import unsafeParseFloat :: String -> Number
-- | Turn NaN's into Nothing
preventNaN :: Number -> Maybe Number
preventNaN n
| isNaN n = Nothing
| otherwise = Just n
-- | Parse an integer in specified base
parseIntBaseN :: Int -> String -> Maybe Int
parseIntBaseN n s = round <$> preventNaN (unsafeParseIntBase s n)
-- | Parse an integer
parseInt10 :: String -> Maybe Int
parseInt10 s = round <$> preventNaN (unsafeParseInt10 s)
-- | Parse a float
parseFloat :: String -> Maybe Number
parseFloat s = preventNaN (unsafeParseFloat s)

109
src/PCC/Main.js Normal file
View file

@ -0,0 +1,109 @@
import fs from "fs";
// import request from "request";
// import path from "path";
import { exec } from "child_process";
// import dotenv from "dotenv";
'use strict';
// dotenv.config();
export const bootstrap = function (year, quest) {
// Left-pad quest!
quest = `${quest}`;
if (quest.length == 1) {
quest = `0${quest}`;
}
// Write file
fs.mkdir(`src/Year${year}`, { recursive: true }, function (err) {
if (err) {
console.log(err);
return;
}
fs.readFile('templates/QuestXX.purs.template', 'utf8', function (err, body) {
let b = body.replace(/__YEAR__/g, year).replace(/__QUEST__/g, quest);
fs.writeFile(`src/Year${year}/Quest${quest}.purs`, b, function (err) {
if (err) {
console.log(err);
return;
}
console.log(`Open src/Year${year}/Quest${quest}.purs in your editor`);
});
});
});
};
export const download = function (year, quest, cookie, filepath, cb) {
// TODO
return;
// const exists = fs.existsSync(filepath);
// if (exists) {
// console.log(`${filepath} already downloaded`);
// if (cb)
// cb();
// }
// else {
// request(`https://everybody.codes/${year}/quest/${quest}/input`, { headers: {
// 'Cookie': `session=${cookie}`
// } }, function (err, res, body) {
// if (err) {
// console.log(err);
// return;
// }
// fs.mkdir(path.dirname(filepath), { recursive: true }, function (err) {
// if (err) {
// console.log(err);
// return;
// }
// fs.writeFile(filepath, body, function (err) {
// if (err) {
// console.log(err);
// return;
// }
// if (cb)
// cb();
// else
// console.log(`Downloaded ${filepath}`);
// });
// });
// });
// }
};
export async function run(year, quest, part, inputfilepath) {
// Left-pad quest!
quest = `${quest}`;
if (quest.length == 1) {
quest = `0${quest}`;
}
exec('spago build', async function (err, stdout, stderr) {
if (err) {
console.log(err);
return;
}
let Module = await import(`../Year${year}.Quest${quest}/index.js`);
fs.readFile(inputfilepath, 'utf8', function (err, body) {
if (err) {
console.log(err);
return;
}
if (part == 1 || !part) {
console.time("Obtained in");
Module.part1(body)();
console.timeEnd("Obtained in");
}
if (part == 2 || !part) {
console.time("Obtained in");
Module.part2(body)();
console.timeEnd("Obtained in");
}
if (part == 3 || !part) {
console.time("Obtained in");
Module.part3(body)();
console.timeEnd("Obtained in");
}
});
});
};
export const pcc_cookie = function () {
return process.env.PCC_COOKIE;
};

102
src/PCC/Main.purs Normal file
View file

@ -0,0 +1,102 @@
module PCC.Main where
import Options.Applicative
import Control.Apply ((<*>))
import Control.Bind (bind, (=<<))
import Data.Foldable (for_)
import Data.Functor ((<$>))
import Data.Maybe (Maybe(..), fromMaybe, optional)
import Data.Semigroup ((<>))
import Data.Show (show)
import Data.Unit (Unit)
import Effect (Effect)
import Effect.Uncurried as EFn
import Node.Path (FilePath)
import PCC.Lib (inputFileLocationYearQuest, testFileLocationYearQuest)
type Year = Int
type Quest = Int
type Part = Int
type Cookie = String
data Command = Bootstrap Year Quest | Download Year Quest Part | Run Year Quest (Maybe Part) | Test Year Quest (Maybe Part)
configOptions :: Parser Command
configOptions = subparser
( command "bootstrap" (info bootstrapOptions ( progDesc "Bootstrap a solution for a particular quest" ))
<> command "download" (info downloadOptions ( progDesc "Download the puzzle input for a particular quest" ))
<> command "run" (info runOptions ( progDesc "Run the solution for a particular quest" ))
<> command "test" (info testOptions ( progDesc "Run the solution with the test input for a particular quest, the test input must be manually saved in a file called test<quest>" ))
)
bootstrapOptions :: Parser Command
bootstrapOptions =
Bootstrap
<$> argument int ( metavar "YEAR" )
<*> argument int ( metavar "QUEST" )
downloadOptions :: Parser Command
downloadOptions =
Download
<$> argument int ( metavar "YEAR" )
<*> argument int ( metavar "QUEST" )
<*> argument int ( metavar "PART" )
runOptions :: Parser Command
runOptions =
Run
<$> argument int ( metavar "YEAR" )
<*> argument int ( metavar "QUEST" )
<*> optional (option int
( long "part"
<> short 'p'
<> metavar "PART"
<> help "Use this to run only one part from that quest. Valid options are 1 or 2 or 3" ))
testOptions :: Parser Command
testOptions =
Test
<$> argument int ( metavar "YEAR" )
<*> argument int ( metavar "QUEST" )
<*> optional (option int
( long "part"
<> short 'p'
<> metavar "PART"
<> help "Use this to run only one part from that quest. Valid options are 1 or 2 or 3" ))
main :: Effect Unit
main = doit =<< execParser opts
opts :: ParserInfo Command
opts = info (configOptions <**> helper)
( fullDesc
<> progDesc "Run a command. One of - bootstrap | download | run | test"
<> header "pcc - A Purescript Coding Competition starter kit" )
foreign import pcc_cookie :: Effect String
doit :: Command -> Effect Unit
doit (Bootstrap year quest) = do
-- doit (Download year quest part)
EFn.runEffectFn2 bootstrap year quest
doit (Download year quest part) = do
cookie <- pcc_cookie
loc <- inputFileLocationYearQuest (show year) (show quest) (show part)
EFn.runEffectFn4 download year quest cookie loc
doit (Run year quest mpart) = case mpart of
Nothing -> runParts year quest [1,2,3]
Just p -> runParts year quest [p]
doit (Test year quest mpart) = do
loc <- testFileLocationYearQuest (show year) (show quest)
EFn.runEffectFn4 run year quest (fromMaybe 0 mpart) loc
runParts :: Int -> Int -> Array Int -> Effect Unit
runParts year quest parts = for_ parts \part -> do
loc <- inputFileLocationYearQuest (show year) (show quest) (show part)
EFn.runEffectFn4 run year quest part loc
foreign import bootstrap :: EFn.EffectFn2 Year Quest Unit
foreign import download :: EFn.EffectFn4 Year Quest Cookie FilePath Unit
-- TODO: 'run' is async
foreign import run :: EFn.EffectFn4 Year Quest Part String Unit

30
src/PCC/Stream.purs Normal file
View file

@ -0,0 +1,30 @@
module PCC.Stream where
import Data.Array as A
import Data.CommutativeRing (class Semiring, (+))
import Data.Functor (class Functor)
import Data.Lazy (Lazy, defer, force)
import Data.Maybe (Maybe(..))
data Stream a = Stream a (Lazy (Stream a))
derive instance functorStream :: Functor Stream
inf :: forall n. Semiring n => n -> n -> Stream n
inf start offset = Stream start (defer \_ -> inf (start+offset) offset)
filter :: forall a. (a -> Boolean) -> Stream a -> Stream a
filter f (Stream a rest) =
let rest' = defer \_ -> filter f (force rest)
in if f a then Stream a rest' else force rest'
find :: forall a. (a -> Boolean) -> Stream a -> a
find f (Stream a rest) = if f a then a else find f (force rest)
-- foldl (with tail recursion), but with short circuit using Maybes
fold :: forall b a. (b -> a -> Maybe b) -> b -> Stream a -> b
fold f b (Stream a l) = case f b a of
Just x -> fold f x (force l)
Nothing -> b
toArray :: forall a. Stream a -> Array a
toArray (Stream a rest) = A.cons a (toArray (force rest))

65
src/Year2024/Quest01.purs Normal file
View file

@ -0,0 +1,65 @@
module Year2024.Quest01 where
import PCC.Lib
import Data.Array as Array
import Data.Boolean (otherwise)
import Data.CommutativeRing ((+))
import Data.Eq ((/=))
import Data.Foldable (sum)
import Data.Function (($))
import Data.Functor (map)
import Data.Ord ((<))
import Data.Semigroup ((<>))
import Data.Show (show)
import Data.Unit (Unit)
import Effect (Effect)
import Effect.Class.Console (log)
--------------------------------------------------------------------------------
-- Write your solutions here
part1 :: String -> Effect Unit
part1 input = do
let val = sum (map toValue (chars input))
log $ "Part 1 ==> " <> show val
part2 :: String -> Effect Unit
part2 input = do
let groups = chunksOf 2 (chars input)
let val = sum (map toValueGroup groups)
log $ "Part 2 ==> " <> show val
part3 :: String -> Effect Unit
part3 input = do
let groups = chunksOf 3 (chars input)
let val = sum (map toValueGroup groups)
log $ "Part 3 ==> " <> show val
--------------------------------------------------------------------------------
toValue :: Char -> Int
toValue = case _ of
'B' -> 1
'C' -> 3
_ -> 0
toValue2 :: Char -> Int
toValue2 = case _ of
'B' -> 1
'C' -> 3
'D' -> 5
_ -> 0
toValueGroup :: Array Char -> Int
toValueGroup grp = do
let members = Array.filter (_ /= 'x') grp
let value = sum (map toValue2 members)
go (Array.length members) value
where
go len val
| len < 2 = val
| len < 3 = val + len
| otherwise = val + len + len

View file

@ -0,0 +1,30 @@
module Year__YEAR__.Quest__QUEST__ where
import PCC.Lib ()
import Data.Function (($))
import Data.Functor (map)
import Data.Semigroup ((<>))
import Data.Unit (Unit)
import Effect (Effect)
import Effect.Class.Console (log)
--------------------------------------------------------------------------------
-- Write your solutions here
part1 :: String -> Effect Unit
part1 input = do
let result = "<TODO>"
log $ "Part 1 ==> " <> result
part2 :: String -> Effect Unit
part2 input = do
let result = "<TODO>"
log $ "Part 2 ==> " <> result
part3 :: String -> Effect Unit
part3 input = do
let result = "<TODO>"
log $ "Part 3 ==> " <> result
--------------------------------------------------------------------------------