Year 2024, Quest 1
This commit is contained in:
commit
b7a6c8a25a
17 changed files with 2370 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal 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
16
package.json
Normal 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
2
pcc
Executable 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
22
pnpm-lock.yaml
Normal 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
1
quests/year2024/quest1-1
Normal file
|
@ -0,0 +1 @@
|
|||
BCBCCCACBCACBBACAAABBCCBAACBBCCACAAACACACCBCABBBCBBBCBCAABBCAACCCBBBBBBAACACCBBCABCCABCAABBBCBCBBCAAACBBCACAACBABCACACBABACABBBBAABBACACBCBBCCCCAAACACAACCACBBCACABAAACABBCCCBCCCABBABBABCBACCBBCACCAACBCBAAAACBABBAABCCABCBCABBBAABAACBACACAACBBCACACABABBBCCCAAABAAAAACACCABAABCCAAAAAACCABBBCACABCBAACCAABBBBCBACAACBBCCABABCBABABBBACCABBABBCBBBCABABCCCBABBABCBBAACCCBBABBAAAAAAAACBCACCCCABCBBCCCCAAACAAABBCCABBBACBBCBCCABACABCBBCBBAACCCCBCCCACAACAAACBCCAACBACCACCACACCCCBBBCCABCBBACCAABBCBCACACBCBCCCCBBAABBABBAABACCBCBABCCBACBBAACCBBACBABACBBCCCABABBBBBAACBBCACCCAAACBCBBBBACBABBABBAAAACBACBAABACCACABBCBAABABBACACAACBBBCBBAAAAABCACCABBACACABBBBACCCBBCCABABBAACABBCBBBAABCCABBCAABBCAABBBBAAAACCBCCABCCAABBACCAACBAACBCCCBBBBCCCACCCAAABCAAAAACCACBAAACAACBCCBBACABCCACABCCBCBCABABCBBACCAAABBCBCBCCCBBCBCBBCACBBCBCAAABACABBAACBCCCACCCCABBAACCBCCCAAABAAAACBCBCCACCACCAABBBCBCBCABCBCCBBCBAACBCBACACCBACBBCCCABACACBAACACBBBABABCAACABCBBACAAAACCACACBCBABBCCBACBCCBCCCBCBCBCCBACACBCABACBCABACCACCBAAAACAACBCAABBC
|
1
quests/year2024/quest1-2
Normal file
1
quests/year2024/quest1-2
Normal file
|
@ -0,0 +1 @@
|
|||
CACBCCBCxADBxCxBBxDAAxBxBCxADAxDCBBBBBADCDCCAAACBAADDCxCDDBCABxBDAAAACxxCACCDDCBBBBCABxBBxBAABDCAACDCBABxDxxBDBAxCDxCCAABAxBDACCDCACxxBxBBxACDBCBDADDDABAABxCBADDxDCDCCxDDDACAxACxABAAABDxDADCABBABDDBACxADAABBCBxACCBCDBDBCCCAADDDxDAADDxBDCCAABDBADBBCAxCBAABCCCCACAAADDDBDxDACAADDBCDxCADCCCBxCCCBADBxBBBBBxACBCDxAxDDDBCCBCCBDDADBDCDDxCDCDDCCACxDBCAAxDCACBDCABAACxBADDBxACBAAABBxBCCxCDDxCDCCDDBBDACAADCDCxCCBDAxDBABDxAACCACABxDCBBCCBADxACDxxxDBxDDDABCCCBCBAAxABADDADxDCBCADxBCBxDDAxACBDxDBCAxCAAxAxACBCxDDACBCxBCBxBDCADABCDxAADCACBBBBBBADCCACBAABxBDCBBDDxDBACDABADxACAxCCxAxBBDBDCABBxBAABDBCxxDBDDAABDCDCADxDDABABBCCBDCCACBCCCxCACBBDCxABAxCADBBDDBCCCACABCCBADDDDDDxCACCBCxDxCBxCDDCCACxDxBBAACBDABDxDABAxDABBDDABBBxBBBCAABCABACDDBCDABDBADDBABxACAxCAxDABDACCABBADBxCACABABAxxACCDADBDCBACDBAABACBAAxABBCCDxDBABDCBBxDDDAADxDBCBCAADBDADCDBBxxCBCxDACCDBCBCDCCABCBBDACADADADBCACBADxDBAACBABDCDDBCBBCDCBDDDDCBACADCAxCACDAACDACDDxBDBBBDBDCBDDCCCDCCxDACBAAxxDCCDBBBBBABBDCBABACCDADDxADDACCCBAABABxCDxDCxDBCxCDADCCAxAxBAxBBxBACDAADxABCBDBACCxBDACDDDCDCCBCDCxCxBCBDxADACCDCDxDAAxABCDDDDCACCABxDDDBBADCBABCDCCBBCBCBxxDBCBxAxBCCCBCAAABxCCDDBBDBBDCCACBABBDCAxAxACDCABCAxDCDABBDBDxxCxCDAAxACCCAABDABBBxDCACCCCDxACDBDBBAxBDxACAABDACCABCBxBBCACCADBBCCCxCBBCDBBBADxCBACBBCCCAxDxAxBDCADABxxAACCBCxDCBDDCxDBBDCCxAAACCBCxACAADCAADAADDCACxBxDDDBDCCxDBDBAADBBCxBCAxDCCBDCABACBDDCCBBCACxCCxBBCCABBxDAACDDAAACDACBBBAABDBACDxxCDABABADxDCCAADBxADADxDxBABDBAxCCBDBxACBABxACxAxABBADBACCBCBDxADBBACBAxBDCACBBDBDBDBDBBxADBCCBACDCACBxDBCCCAACDBDBDBAAACxDDBADAxDxDCCAxxDDCAABBxBBCCCAxAADxDBABBBBCDBBDBBDCBxBCBxDCxADDACBBCCBAADCBAxDCxxBBDxCBDDDADADADABDCDABxADBDDADCAAABADBBxADDDDDxBBDDCBBCDxCBxxCACCBCDACDDCDCxxDxDCACxCAADCCDAACBAADCxxBABDxCxBABDCADBDADACBAABACxCCBBBBBABDCBABDDACABDCBCDABCDDCBCCCxDDCABCAxCDCBACDCBCDDAAAADCCBDxBACxDDADDDAxCDDCDADAABxxBDCCDBCDxBDABCDDABCBCADACAAADxCBDAACABDADBBAABxBCCABCDxBCADBACDDAxCxxCDCxDADBADACDCAxDBAACxDDBCxBDBADCDDBACCACDDCBAACCCCCAxDCCAACCADBBCDDBCABBDACADAAACBCxCxxDCBDBDDDBDDBBBAACADxBDCACBCAx
|
1
quests/year2024/quest1-3
Normal file
1
quests/year2024/quest1-3
Normal file
File diff suppressed because one or more lines are too long
1633
spago.lock
Normal file
1633
spago.lock
Normal file
File diff suppressed because it is too large
Load diff
52
spago.yaml
Normal file
52
spago.yaml
Normal 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
10
src/Main.purs
Normal 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
20
src/PCC/Lib.js
Normal 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
264
src/PCC/Lib.purs
Normal 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
109
src/PCC/Main.js
Normal 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
102
src/PCC/Main.purs
Normal 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
30
src/PCC/Stream.purs
Normal 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
65
src/Year2024/Quest01.purs
Normal 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
|
||||
|
30
templates/QuestXX.purs.template
Normal file
30
templates/QuestXX.purs.template
Normal 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
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
Loading…
Reference in a new issue