Compare commits

29 Commits

Author SHA1 Message Date
Smaug123
71e6d8479a strace
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:59:45 +00:00
Smaug123
f31a856838 why
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:52:37 +00:00
Smaug123
10e2602b50 why
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:45:04 +00:00
Smaug123
729a205dc8 sigh
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:40:02 +00:00
Smaug123
3499326730 sigh
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:35:59 +00:00
Smaug123
b67efa2198 hmm
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:31:05 +00:00
Smaug123
5c445e8175 mooore
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:24:13 +00:00
Smaug123
a47d3e635e pls pipeline
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:18:17 +00:00
Smaug123
53e247d222 Go
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
ci/woodpecker/manual/all-checks-complete Pipeline was successful
2023-12-08 22:16:15 +00:00
Smaug123
78680ccaf8 Quote
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:11:03 +00:00
Smaug123
a0b9e50f50 debug
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 22:04:54 +00:00
Smaug123
2510873e5f Oops
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 21:57:12 +00:00
Smaug123
9d81279173 How about this
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 21:52:32 +00:00
Smaug123
7d1ba3e6a1 Go
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 21:35:50 +00:00
Smaug123
e8fa607279 Sigh, here we go
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-08 21:30:58 +00:00
d94663ae0e Day 8 (#8)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Blown way through my time budget and there's a bunch of low hanging fruit (data dependencies in LCM, for example) but I'm late for work.

Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #8
2023-12-08 09:07:04 +00:00
bcd2bb6349 Speed up day 7 a bit (#7)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
I'm going mad, why am I doing this

Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #7
2023-12-07 23:52:34 +00:00
2fbdf2c362 Day 7 (#6)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #6
2023-12-07 09:32:21 +00:00
786a7eba8e Day 6 (#5)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #5
2023-12-06 09:06:05 +00:00
9c48e5fa96 Day 5 (#4)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #4
2023-12-05 20:26:07 +00:00
c8c1cdc950 Day 4 (#2)
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/all-checks-complete Pipeline was successful
Reviewed-on: #2
2023-12-04 19:29:11 +00:00
9454c0ac1a Woodpecker (#1)
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/push/all-checks-complete unknown status
Co-authored-by: Smaug123 <patrick+github@patrickstevens.co.uk>
Reviewed-on: #1
2023-12-03 17:32:16 +00:00
Smaug123
ee38b17138 README 2023-12-03 17:24:47 +00:00
Smaug123
5c451057bf Licence MIT 2023-12-03 17:21:26 +00:00
Smaug123
4a1d9d1cae Better program 2023-12-03 17:20:55 +00:00
Smaug123
a48aaa78b1 Day 2 2023-12-03 17:11:15 +00:00
Smaug123
1f505a7cce Day 1 in F# 2023-12-03 16:56:38 +00:00
Smaug123
a637f79bf1 Format and tests 2023-12-03 16:31:46 +00:00
Smaug123
3a8061e28d Add day 3 2023-12-03 14:35:47 +00:00
46 changed files with 2527 additions and 0 deletions

12
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"fantomas": {
"version": "6.2.3",
"commands": [
"fantomas"
]
}
}
}

41
.editorconfig Normal file
View File

@@ -0,0 +1,41 @@
root=true
[*]
charset=utf-8
end_of_line=crlf
trim_trailing_whitespace=true
insert_final_newline=true
indent_style=space
indent_size=4
# ReSharper properties
resharper_xml_indent_size=2
resharper_xml_max_line_length=100
resharper_xml_tab_width=2
[*.{csproj,fsproj,sqlproj,targets,props,ts,tsx,css,json}]
indent_style=space
indent_size=2
[*.{fs,fsi}]
fsharp_bar_before_discriminated_union_declaration=true
fsharp_space_before_uppercase_invocation=true
fsharp_space_before_class_constructor=true
fsharp_space_before_member=true
fsharp_space_before_colon=true
fsharp_space_before_semicolon=true
fsharp_multiline_bracket_style=aligned
fsharp_newline_between_type_definition_and_members=true
fsharp_align_function_signature_to_indentation=true
fsharp_alternative_long_member_definitions=true
fsharp_multi_line_lambda_closing_newline=true
fsharp_experimental_keep_indent_in_branch=true
fsharp_max_value_binding_width=80
fsharp_max_record_width=0
max_line_length=120
end_of_line=lf
[*.{appxmanifest,build,dtd,nuspec,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=2
tab_width=2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.txt text eol=lf

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
bin/
obj/
riderModule.iml
_ReSharper.Caches/
.idea/
*.user
*.DotSettings
.DS_Store
result
.profile*
inputs/

View File

@@ -0,0 +1,10 @@
steps:
echo:
image: alpine
commands:
- echo "All required checks complete"
depends_on:
- build
skip_clone: true

32
.woodpecker/.build.yml Normal file
View File

@@ -0,0 +1,32 @@
steps:
build:
image: nixos/nix
commands:
- echo 'experimental-features = flakes nix-command' >> /etc/nix/nix.conf
- "nix develop --command dotnet publish AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.fsproj --configuration Release -p:PublishAot=true || echo 'First publish failed'"
- "nix develop --command sh -c 'patchelf --set-interpreter $LINKER_PATH /tmp/dotnet-home/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/8.0.0/tools/ilc'"
- "ls -al /tmp/dotnet-home/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/8.0.0/tools/ilc"
- "chmod a+x /tmp/dotnet-home/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/8.0.0/tools/ilc"
- "ls -al /tmp/dotnet-home/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/8.0.0/tools/ilc"
- "whoami"
- "cp -r AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/obj /tmp/obj"
- "nix develop --command sh -c 'ls -la $LINKER_PATH'"
- "nix develop --command sh -c 'strace /tmp/dotnet-home/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/8.0.0/tools/ilc /tmp/obj/Release/net8.0/linux-x64/native/AdventOfCode2023.FSharp.ilc.rsp'"
- "nix develop --command dotnet publish AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.fsproj --configuration Release -p:PublishAot=true"
# Lint
- "nix flake check"
# Test
- nix develop --command dotnet test AdventOfCode2023.FSharp
- nix develop --command dotnet test AdventOfCode2023.FSharp --configuration Release
- nix develop --command alejandra --check .
- nix develop --command dotnet tool restore
- nix develop --command dotnet fantomas --check .
# TODO: if https://github.com/dotnet/sdk/issues/37295 ever gets fixed, remove the PublishAot=false
- "nix develop --command dotnet publish AdventOfCode2023.FSharp/AdventOfCode2023.FSharp/AdventOfCode2023.FSharp.fsproj --configuration Release -p:PublishAot=false -p:SelfContained=true"
- '$(find . -type f -name AdventOfCode2023.FSharp | grep Release | grep publish) "$(pwd)/AdventOfCode2023.FSharp/Test/samples"'
when:
- event: "push"
evaluate: 'CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
- event: "pull_request"

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Arr2D.fs" />
<Compile Include="ResizeArray.fs" />
<Compile Include="EfficientString.fs" />
<Compile Include="Arithmetic.fs" />
<Compile Include="Day1.fs" />
<Compile Include="Day2.fs" />
<Compile Include="Day3.fs" />
<Compile Include="Day4.fs" />
<Compile Include="Day5.fs" />
<Compile Include="Day6.fs" />
<Compile Include="Day7.fs" />
<Compile Include="Day8.fs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,67 @@
namespace AdventOfCode2023
[<RequireQualifiedAccess>]
module Arithmetic =
[<Struct>]
type EuclidResult<'a> =
{
Hcf : 'a
A : 'a
B : 'a
}
/// Compute floor(sqrt(i)).
let inline sqrt (i : ^a) =
if i <= LanguagePrimitives.GenericOne then
i
else
let rec go start =
let next = start + LanguagePrimitives.GenericOne
let sqr = next * next
if sqr < LanguagePrimitives.GenericZero then
// Overflow attempted, so the sqrt is between start and next
start
elif i < sqr then
start
elif i = sqr then
next
else
go next
go LanguagePrimitives.GenericOne
/// Find Hcf, A, B s.t. A * a + B * b = Hcf, and Hcf is the highest common factor of a and b.
let inline euclideanAlgorithm (a : ^a) (b : ^a) : EuclidResult< ^a > =
let rec go rMin1 r sMin1 s tMin1 t =
if r = LanguagePrimitives.GenericZero then
{
Hcf = rMin1
A = sMin1
B = tMin1
}
else
let newQ = rMin1 / r
go r (rMin1 - newQ * r) s (sMin1 - newQ * s) t (tMin1 - newQ * t)
let maxA = max a b
let minB = min a b
let result =
go
maxA
minB
LanguagePrimitives.GenericOne
LanguagePrimitives.GenericZero
LanguagePrimitives.GenericZero
LanguagePrimitives.GenericOne
if a = maxA then
result
else
{
Hcf = result.Hcf
A = result.B
B = result.A
}

View File

@@ -0,0 +1,102 @@
namespace AdventOfCode2023
#if DEBUG
#else
#nowarn "9"
#endif
open Microsoft.FSharp.NativeInterop
[<Struct>]
#if DEBUG
type Arr2D<'a> =
{
Elements : 'a array
Width : int
}
member this.Height = this.Elements.Length / this.Width
#else
type Arr2D<'a when 'a : unmanaged> =
{
Elements : nativeptr<'a>
Length : int
Width : int
}
member this.Height = this.Length / this.Width
#endif
[<RequireQualifiedAccess>]
module Arr2D =
/// It's faster to iterate forward over the first argument, `x`.
let inline get (arr : Arr2D<'a>) (x : int) (y : int) : 'a =
#if DEBUG
arr.Elements.[y * arr.Width + x]
#else
NativePtr.get arr.Elements (y * arr.Width + x)
#endif
let inline set (arr : Arr2D<'a>) (x : int) (y : int) (newVal : 'a) : unit =
#if DEBUG
arr.Elements.[y * arr.Width + x] <- newVal
#else
NativePtr.write (NativePtr.add arr.Elements (y * arr.Width + x)) newVal
#endif
#if DEBUG
let create (width : int) (height : int) (value : 'a) : Arr2D<'a> =
let arr = Array.create (width * height) value
{
Width = width
Elements = arr
}
#else
/// The input array must be at least of size width * height
let create (arr : nativeptr<'a>) (width : int) (height : int) (value : 'a) : Arr2D<'a> =
{
Width = width
Elements = arr
Length = width * height
}
#endif
[<RequiresExplicitTypeArguments>]
#if DEBUG
let zeroCreate<'a when 'a : unmanaged> (width : int) (height : int) : Arr2D<'a> =
{
Elements = Array.zeroCreate (width * height)
Width = width
}
#else
let zeroCreate<'a when 'a : unmanaged> (elts : nativeptr<'a>) (width : int) (height : int) : Arr2D<'a> =
{
Elements = elts
Width = width
Length = width * height
}
#endif
/// The closure is given x and then y.
#if DEBUG
let inline init (width : int) (height : int) (f : int -> int -> 'a) : Arr2D<'a> =
let result = zeroCreate<'a> width height
#else
let inline init (arr : nativeptr<'a>) (width : int) (height : int) (f : int -> int -> 'a) : Arr2D<'a> =
let result = zeroCreate<'a> arr width height
#endif
for y = 0 to height - 1 do
for x = 0 to width - 1 do
set result x y (f x y)
result
let inline clear (a : Arr2D<'a>) : unit =
#if DEBUG
System.Array.Clear a.Elements
#else
NativePtr.initBlock a.Elements 0uy (uint32 sizeof<'a> * uint32 a.Length)
#endif

View File

@@ -0,0 +1,111 @@
namespace AdventOfCode2023
open System
[<RequireQualifiedAccess>]
module Day1 =
let inline firstDigit (s : ReadOnlySpan<char>) =
let pos = s.IndexOfAnyInRange ('0', '9')
byte s.[pos] - byte '0'
// No surrogate pairs please!
let inline lastDigit (s : ReadOnlySpan<char>) =
let pos = s.LastIndexOfAnyInRange ('0', '9')
byte s.[pos] - byte '0'
let part1 (s : string) =
use enum = StringSplitEnumerator.make '\n' s
let mutable total = 0
for line in enum do
if not line.IsEmpty then
let firstDigit = firstDigit line
let lastDigit = lastDigit line
total <- total + int (lastDigit + 10uy * firstDigit)
total
let isDigitSpelled (s : ReadOnlySpan<char>) (pos : int) (answer : byref<byte>) =
// Can't be bothered to write a jump-table compiler
if s.[pos] >= '0' && s.[pos] <= '9' then
answer <- byte s.[pos] - byte '0'
else if s.[pos] = 'o' then
if pos + 2 < s.Length && s.[pos + 1] = 'n' && s.[pos + 2] = 'e' then
answer <- 1uy
elif s.[pos] = 't' then
if pos + 2 < s.Length && s.[pos + 1] = 'w' && s.[pos + 2] = 'o' then
answer <- 2uy
elif
pos + 4 < s.Length
&& s.[pos + 1] = 'h'
&& s.[pos + 2] = 'r'
&& s.[pos + 3] = 'e'
&& s.[pos + 4] = 'e'
then
answer <- 3uy
elif s.[pos] = 'f' then
if pos + 3 < s.Length then
if s.[pos + 1] = 'o' && s.[pos + 2] = 'u' && s.[pos + 3] = 'r' then
answer <- 4uy
elif s.[pos + 1] = 'i' && s.[pos + 2] = 'v' && s.[pos + 3] = 'e' then
answer <- 5uy
elif s.[pos] = 's' then
if pos + 2 < s.Length && s.[pos + 1] = 'i' && s.[pos + 2] = 'x' then
answer <- 6uy
elif
pos + 4 < s.Length
&& s.[pos + 1] = 'e'
&& s.[pos + 2] = 'v'
&& s.[pos + 3] = 'e'
&& s.[pos + 4] = 'n'
then
answer <- 7uy
elif s.[pos] = 'e' then
if
pos + 4 < s.Length
&& s.[pos + 1] = 'i'
&& s.[pos + 2] = 'g'
&& s.[pos + 3] = 'h'
&& s.[pos + 4] = 't'
then
answer <- 8uy
elif s.[pos] = 'n' then
if
pos + 3 < s.Length
&& s.[pos + 1] = 'i'
&& s.[pos + 2] = 'n'
&& s.[pos + 3] = 'e'
then
answer <- 9uy
let firstDigitIncSpelled (s : ReadOnlySpan<char>) =
let mutable pos = 0
let mutable answer = 255uy
while answer = 255uy do
isDigitSpelled s pos &answer
pos <- pos + 1
answer
let lastDigitIncSpelled (s : ReadOnlySpan<char>) =
let mutable pos = s.Length - 1
let mutable answer = 255uy
while answer = 255uy do
isDigitSpelled s pos &answer
pos <- pos - 1
answer
let part2 (s : string) =
use enum = StringSplitEnumerator.make '\n' s
let mutable total = 0
for line in enum do
if not line.IsEmpty then
total <- total + int (10uy * firstDigitIncSpelled line + lastDigitIncSpelled line)
total

View File

@@ -0,0 +1,64 @@
namespace AdventOfCode2023
open System
open System.Globalization
[<RequireQualifiedAccess>]
module Day2 =
let inline parseInt (s : ReadOnlySpan<char>) : int =
Int32.Parse (s, NumberStyles.None, CultureInfo.InvariantCulture)
let part1 (s : string) =
use lines = StringSplitEnumerator.make '\n' s
let mutable answer = 0
for line in lines do
if not line.IsEmpty then
use mutable words = StringSplitEnumerator.make' ' ' line
let mutable prevWord = ReadOnlySpan<char>.Empty
let mutable isOk = true
while isOk && words.MoveNext () do
match words.Current.[0] with
| 'b' ->
if parseInt prevWord > 14 then
isOk <- false
| 'r' ->
if parseInt prevWord > 12 then
isOk <- false
| 'g' ->
if parseInt prevWord > 13 then
isOk <- false
| _ -> ()
prevWord <- words.Current
if isOk then
answer <- answer + parseInt (line.Slice (5, line.IndexOf ':' - 5))
answer
let part2 (s : string) =
use lines = StringSplitEnumerator.make '\n' s
let mutable answer = 0
for line in lines do
if not line.IsEmpty then
let mutable reds = 0
let mutable blues = 0
let mutable greens = 0
use mutable words = StringSplitEnumerator.make' ' ' line
let mutable prevWord = ReadOnlySpan<char>.Empty
while words.MoveNext () do
match words.Current.[0] with
| 'b' -> blues <- max blues (parseInt prevWord)
| 'r' -> reds <- max reds (parseInt prevWord)
| 'g' -> greens <- max greens (parseInt prevWord)
| _ -> ()
prevWord <- words.Current
answer <- answer + (reds * greens * blues)
answer

View File

@@ -0,0 +1,153 @@
namespace AdventOfCode2023
open System.Collections.Generic
[<RequireQualifiedAccess>]
module Day3 =
let inline private isSymbol (i : byte) = i > 200uy
let inline private isGear (i : byte) = i = 255uy
/// Returns the parsed board as a buffer, the length of the buffer (there may be garbage at the end), and
/// the number of lines the resulting 2D array has.
let parse (fileContents : byte[]) =
let mutable lineCount = 0
let mutable len = 0
let resultArr = Array.zeroCreate fileContents.Length
for b in fileContents do
if b = byte '.' then
resultArr.[len] <- 100uy
len <- len + 1
elif b = byte '*' then
resultArr.[len] <- 255uy
len <- len + 1
elif byte '0' <= b && b <= byte '9' then
resultArr.[len] <- b - byte '0'
len <- len + 1
elif b = 10uy then
lineCount <- lineCount + 1
else
resultArr.[len] <- 254uy
len <- len + 1
resultArr, len, lineCount
let part1 (contents : Arr2D<byte>) =
let lineLength = contents.Width
let isNearSymbol (row : int) (numStart : int) (curCol : int) : bool =
let mutable isNearSymbol = false
if row > 0 then
for col = max (numStart - 1) 0 to min curCol (lineLength - 1) do
if isSymbol (Arr2D.get contents col (row - 1)) then
isNearSymbol <- true
if row < contents.Height - 1 then
for col = max (numStart - 1) 0 to min curCol (lineLength - 1) do
if isSymbol (Arr2D.get contents col (row + 1)) then
isNearSymbol <- true
if
(numStart > 0 && isSymbol (Arr2D.get contents (numStart - 1) row))
|| (curCol < lineLength && isSymbol (Arr2D.get contents curCol row))
then
isNearSymbol <- true
isNearSymbol
let mutable total = 0
for row = 0 to contents.Height - 1 do
let mutable currNum = 0
let mutable numStart = -1
for col = 0 to lineLength - 1 do
if Arr2D.get contents col row < 10uy then
if numStart = -1 then
numStart <- col
currNum <- currNum * 10 + int (Arr2D.get contents col row)
elif numStart > -1 then
if isNearSymbol row numStart col then
total <- total + currNum
currNum <- 0
numStart <- -1
if numStart >= 0 then
if isNearSymbol row numStart lineLength then
total <- total + currNum
currNum <- 0
numStart <- -1
total
let part2 (contents : Arr2D<byte>) =
let lineLength = contents.Width
let isNearGear (row : int) (numStart : int) (curCol : int) : (int * int) IReadOnlyList =
let gearsNear = ResizeArray ()
if row > 0 then
for col = max (numStart - 1) 0 to min curCol (lineLength - 1) do
if isGear (Arr2D.get contents col (row - 1)) then
gearsNear.Add (row - 1, col)
if row < lineLength - 1 then
for col = max (numStart - 1) 0 to min curCol (lineLength - 1) do
if isGear (Arr2D.get contents col (row + 1)) then
gearsNear.Add (row + 1, col)
if (numStart > 0 && isGear (Arr2D.get contents (numStart - 1) row)) then
gearsNear.Add (row, numStart - 1)
if (curCol < lineLength && isGear (Arr2D.get contents curCol row)) then
gearsNear.Add (row, curCol)
gearsNear
let gears = Dictionary<int * int, ResizeArray<int>> ()
let addGear (gearPos : int * int) (num : int) =
match gears.TryGetValue gearPos with
| false, _ ->
let arr = ResizeArray ()
arr.Add num
gears.Add (gearPos, arr)
| true, arr when arr.Count < 3 -> arr.Add num
| _ -> ()
for row = 0 to contents.Height - 1 do
let mutable currNum = 0
let mutable numStart = -1
for col = 0 to lineLength - 1 do
if Arr2D.get contents col row < 10uy then
if numStart = -1 then
numStart <- col
currNum <- currNum * 10 + int (Arr2D.get contents col row)
elif numStart > -1 then
for gearPos in isNearGear row numStart col do
addGear gearPos currNum
currNum <- 0
numStart <- -1
if numStart >= 0 then
for gearPos in isNearGear row numStart lineLength do
addGear gearPos currNum
currNum <- 0
numStart <- -1
let mutable answer = 0
for gears in gears.Values do
if gears.Count = 2 then
answer <- answer + gears.[0] * gears.[1]
answer

View File

@@ -0,0 +1,101 @@
namespace AdventOfCode2023
open System
open System.Globalization
[<RequireQualifiedAccess>]
module Day4 =
let inline parseByte (chars : ReadOnlySpan<char>) : byte =
Byte.Parse (chars, NumberStyles.None, NumberFormatInfo.InvariantInfo)
//let mutable answer = 0uy
//for c in chars do
// answer <- answer * 10uy + (byte c - 48uy)
//answer
let part1 (s : string) =
use lines = StringSplitEnumerator.make '\n' s
let mutable total = 0
let winningNumbers = ResizeArray<byte> ()
for line in lines do
if not (line.IsWhiteSpace ()) then
let mutable accumulatingWinning = true
winningNumbers.Clear ()
use mutable split = StringSplitEnumerator.make' ' ' line
StringSplitEnumerator.chomp "Card" &split
while split.Current.IsEmpty || split.Current.[split.Current.Length - 1] <> ':' do
split.MoveNext () |> ignore
split.MoveNext () |> ignore
while accumulatingWinning do
while split.Current.IsEmpty do
split.MoveNext () |> ignore
if split.Current.[0] = '|' then
accumulatingWinning <- false
else
winningNumbers.Add (parseByte split.Current)
split.MoveNext () |> ignore
let mutable answer = 0
while split.MoveNext () do
if not split.Current.IsEmpty then
let n = parseByte split.Current
if winningNumbers.Contains n then
answer <- answer + 1
if answer > 0 then
total <- total + (1 <<< (answer - 1))
total
let part2 (s : string) =
use lines = StringSplitEnumerator.make '\n' s
let winningNumbers = ResizeArray<byte> ()
let winners = ResizeArray<int> ()
for line in lines do
if not (line.IsWhiteSpace ()) then
let mutable accumulatingWinning = true
winningNumbers.Clear ()
use mutable split = StringSplitEnumerator.make' ' ' line
StringSplitEnumerator.chomp "Card" &split
while split.Current.IsEmpty || split.Current.[split.Current.Length - 1] <> ':' do
split.MoveNext () |> ignore
split.MoveNext () |> ignore
while accumulatingWinning do
while split.Current.IsEmpty do
split.MoveNext () |> ignore
if split.Current.[0] = '|' then
accumulatingWinning <- false
else
winningNumbers.Add (parseByte split.Current)
split.MoveNext () |> ignore
let mutable answer = 0
while split.MoveNext () do
if not split.Current.IsEmpty then
let n = parseByte split.Current
if winningNumbers.Contains n then
answer <- answer + 1
winners.Add answer
let ans = Array.create winners.Count 1
for i = 0 to winners.Count - 1 do
for j = i + 1 to winners.[i] + i do
ans.[j] <- ans.[j] + ans.[i]
ans |> Array.sum

View File

@@ -0,0 +1,191 @@
namespace AdventOfCode2023
open System
[<Struct>]
type Range =
{
SourceStart : uint32
DestStart : uint32
Len : uint32
}
[<RequireQualifiedAccess>]
module Day5 =
let parse (s : string) =
use mutable lines = StringSplitEnumerator.make '\n' s
lines.MoveNext () |> ignore
let seeds =
use mutable line1 = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "seeds:" &line1
let result = ResizeArray ()
while line1.MoveNext () do
result.Add (UInt32.Parse line1.Current)
result.ToArray ()
lines.MoveNext () |> ignore
let mappings = ResizeArray ()
let mutable currentMapping = null
for line in lines do
if line.IsEmpty then
if not (isNull currentMapping) then
mappings.Add currentMapping
currentMapping <- null
else if isNull currentMapping then
currentMapping <- ResizeArray ()
else
use mutable line = StringSplitEnumerator.make' ' ' line
let destStart = StringSplitEnumerator.consumeU32 &line
let sourceStart = StringSplitEnumerator.consumeU32 &line
let rangeLen = StringSplitEnumerator.consumeU32 &line
{
SourceStart = sourceStart
DestStart = destStart
Len = rangeLen
}
|> currentMapping.Add
seeds, mappings
let part1 (s : string) =
let seeds, mappings = parse s
let mutable best = UInt32.MaxValue
for seed in seeds do
let mutable remapped = seed
for map in mappings do
let mutable hasRemappedThisLayer = false
for interval in map do
if not hasRemappedThisLayer then
if
interval.SourceStart <= remapped
&& remapped - interval.SourceStart < interval.Len
then
hasRemappedThisLayer <- true
remapped <- remapped + (interval.DestStart - interval.SourceStart)
if remapped < best then
best <- remapped
best
// The input ranges are inclusive at both ends.
// Returns any range we didn't map.
let private split
(resultStarts : ResizeArray<uint32>)
(resultEnds : ResizeArray<uint32>)
start
finish
(rangeFromLayer : Range)
: (uint32 * uint32 * (uint32 * uint32) voption) voption
=
let low = rangeFromLayer.SourceStart
let high = rangeFromLayer.SourceStart + rangeFromLayer.Len - 1ul
if low <= start then
if finish <= high then
// low ... start .. finish .. high
// so the entire input range gets mapped down
resultStarts.Add (start + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
resultEnds.Add (finish + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
ValueNone
elif start <= high then
// low .. start .. high .. finish
// so start .. high gets mapped down
// and high + 1 .. finish stays where it is.
// high < finish is already guaranteed by previous if block.
resultStarts.Add (start + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
resultEnds.Add (high + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
ValueSome (high + 1ul, finish, ValueNone)
else
ValueSome (start, finish, ValueNone)
else if high <= finish then
// start .. low .. high .. finish
// so start .. low - 1 stays where it is
// low .. high gets mapped down
// and high + 1 .. finish stays where it is
resultStarts.Add (low + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
resultEnds.Add (high + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
ValueSome (start, low - 1ul, ValueSome (high + 1ul, finish))
elif low < finish then
// start .. low .. finish .. high
// so start .. low - 1 stays where it is
// and low .. finish gets mapped down
resultStarts.Add (low + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
resultEnds.Add (finish + rangeFromLayer.DestStart - rangeFromLayer.SourceStart)
ValueSome (start, low - 1ul, ValueNone)
else
ValueSome (start, finish, ValueNone)
let part2 (s : string) : uint32 =
let seeds, mappings = parse s
let mutable intervalStarts = ResizeArray ()
let mutable intervalEnds = ResizeArray ()
for i = 0 to (seeds.Length - 1) / 2 do
intervalStarts.Add seeds.[2 * i]
intervalEnds.Add (seeds.[2 * i + 1] + seeds.[2 * i] - 1ul)
let mutable nextIntervalStarts = ResizeArray ()
let mutable nextIntervalEnds = ResizeArray ()
for mapLayer in mappings do
let mutable i = 0
while i < intervalStarts.Count do
// split interval according to every map
let mutable allMoved = false
let mutable currentRange = 0
while not allMoved && currentRange < mapLayer.Count do
let range = mapLayer.[currentRange]
// range is e.g. 50 98 2, i.e. "98-99 goes to 50-51"
match split nextIntervalStarts nextIntervalEnds intervalStarts.[i] intervalEnds.[i] range with
| ValueNone -> allMoved <- true
| ValueSome (start, finish, v) ->
intervalStarts.[i] <- start
intervalEnds.[i] <- finish
match v with
| ValueNone -> ()
| ValueSome (start, finish) ->
intervalStarts.Add start
intervalEnds.Add finish
currentRange <- currentRange + 1
if not allMoved then
nextIntervalStarts.Add intervalStarts.[i]
nextIntervalEnds.Add intervalEnds.[i]
i <- i + 1
let oldIntervals = intervalStarts
oldIntervals.Clear ()
intervalStarts <- nextIntervalStarts
nextIntervalStarts <- oldIntervals
let oldIntervals = intervalEnds
oldIntervals.Clear ()
intervalEnds <- nextIntervalEnds
nextIntervalEnds <- oldIntervals
// SIMD go brrr
System.Linq.Enumerable.Min intervalStarts

View File

@@ -0,0 +1,86 @@
namespace AdventOfCode2023
open System
[<RequireQualifiedAccess>]
module Day6 =
let parse (s : string) =
use mutable lines = StringSplitEnumerator.make '\n' s
let times =
lines.MoveNext () |> ignore
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "Time:" &line
line.MoveNext () |> ignore
let times = ResizeArray ()
while line.MoveNext () do
if not line.Current.IsEmpty then
times.Add (UInt64.Parse line.Current)
times
let distance =
lines.MoveNext () |> ignore
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "Distance:" &line
line.MoveNext () |> ignore
let distance = ResizeArray ()
while line.MoveNext () do
if not line.Current.IsEmpty then
distance.Add (UInt64.Parse line.Current)
distance
times, distance
let furthest (distance : uint64) (toBeat : uint64) =
// Count i in [1 .. distance - 1] such that (distance - i) * i > toBeat
// i.e. such that distance * i - i * i > toBeat
// -i^2 + distance * i - toBeat = 0 when:
// i = (distance +- sqrt(distance^2 - 4 * toBeat)) / 2
let distFloat = float distance
let inside = sqrt (distFloat * distFloat - 4.0 * float toBeat)
let limit1 = (distFloat + inside) / 2.0
let limit2 = (distFloat - sqrt (distFloat * distFloat - 4.0 * float toBeat)) / 2.0
// round limit2 up and limit1 down
let limit1 = uint64 (floor limit1)
let limit2 = uint64 (ceil limit2)
// cope with edge case of an exact square
if (uint64 inside) * (uint64 inside) = uint64 distance * uint64 distance - 4uL * uint64 toBeat then
limit1 - limit2 - 1uL
else
limit1 - limit2 + 1uL
let part1 (s : string) =
let times, distance = parse s
let mutable answer = 1uL
for i = 0 to times.Count - 1 do
let time = times.[i]
let distance = distance.[i]
let winners = furthest time distance
answer <- answer * winners
answer
let concat (digits : ResizeArray<uint64>) : uint64 =
let mutable answer = 0uL
for digit in digits do
let mutable power = 10uL
while digit >= power do
power <- power * 10uL
answer <- answer * power + digit
answer
let part2 (s : string) =
let times, distance = parse s
let concatTime = concat times
let concatDist = concat distance
furthest concatTime concatDist

View File

@@ -0,0 +1,215 @@
namespace AdventOfCode2023
open System
open System.Globalization
open AdventOfCode2023.ResizeArray
type Hand =
| Five = 6
| Four = 5
| FullHouse = 4
| Three = 3
| TwoPairs = 2
| Pair = 1
| High = 0
type HandContents =
{
First : byte
Second : byte
Third : byte
Fourth : byte
Fifth : byte
}
[<RequireQualifiedAccess>]
module Day7 =
[<Literal>]
let joker = 0uy
let inline toByte (adjustJoker : bool) (c : char) : byte =
if c <= '9' then byte c - byte '1'
elif c = 'T' then 9uy
elif c = 'J' then (if adjustJoker then joker else 10uy)
elif c = 'Q' then 11uy
elif c = 'K' then 12uy
elif c = 'A' then 13uy
else failwithf "could not parse: %c" c
let inline private updateState (tallies : ResizeArray<_>) newNum =
let mutable isAdded = false
for i = 0 to tallies.Count - 1 do
if fst tallies.[i] = newNum then
tallies.[i] <- (fst tallies.[i], snd tallies.[i] + 1)
isAdded <- true
if not isAdded then
tallies.Add (newNum, 1)
type RankedHand = uint32
[<Literal>]
let fourteen = 14ul
[<Literal>]
let fourteenFive = fourteen * fourteen * fourteen * fourteen * fourteen
[<Literal>]
let fourteenFour = fourteen * fourteen * fourteen * fourteen
[<Literal>]
let fourteenThree = fourteen * fourteen * fourteen
[<Literal>]
let fourteenTwo = fourteen * fourteen
let toInt (hand : Hand) (contents : HandContents) : RankedHand =
uint32 hand * fourteenFive
+ uint32 contents.First * fourteenFour
+ uint32 contents.Second * fourteenThree
+ uint32 contents.Third * fourteenTwo
+ uint32 contents.Fourth * fourteen
+ uint32 contents.Fifth
let parseHand (tallyBuffer : ResizeArray<_>) (adjustJoker : bool) (s : ReadOnlySpan<char>) : RankedHand =
let contents =
{
First = toByte adjustJoker s.[0]
Second = toByte adjustJoker s.[1]
Third = toByte adjustJoker s.[2]
Fourth = toByte adjustJoker s.[3]
Fifth = toByte adjustJoker s.[4]
}
tallyBuffer.Clear ()
tallyBuffer.Add (contents.First, 1)
updateState tallyBuffer contents.Second
updateState tallyBuffer contents.Third
updateState tallyBuffer contents.Fourth
updateState tallyBuffer contents.Fifth
let jokerCount, jokerPos =
if not adjustJoker then
0, -1
else
let mutable jokerCount = 0
let mutable jokerPos = 0
while jokerPos < tallyBuffer.Count && jokerCount = 0 do
let card, tally = tallyBuffer.[jokerPos]
if card = joker then
jokerCount <- tally
else
jokerPos <- jokerPos + 1
jokerCount, jokerPos
let hand =
if jokerCount > 0 then
match tallyBuffer.Count with
| 1 ->
// Five jokers
Hand.Five
| 2 ->
// Jokers plus one other card type
Hand.Five
| 3 ->
// Jokers plus two other card types. Either full house, or four of a kind
if jokerCount >= 2 then
// JJABB or JJJAB
Hand.Four
else if
// JAABB or JAAAB
jokerPos <> 0
then
if snd tallyBuffer.[0] = 2 then
Hand.FullHouse
else
Hand.Four
else if snd tallyBuffer.[1] = 2 then
Hand.FullHouse
else
Hand.Four
| 4 ->
// Jokers plus three other card types, exactly one of which therefore is a two-of.
Hand.Three
| 5 ->
// Five different cards, one of which is a joker.
Hand.Pair
| _ -> failwith "bad tallyBuffer"
elif tallyBuffer.Count = 1 then
Hand.Five
elif tallyBuffer.Count = 2 then
// AAAAB or AAABB
if snd tallyBuffer.[0] = 3 || snd tallyBuffer.[0] = 2 then
Hand.FullHouse
else
Hand.Four
elif tallyBuffer.Count = 3 then
// AAABC or AABBC
if snd tallyBuffer.[0] = 3 then Hand.Three
elif snd tallyBuffer.[0] = 2 then Hand.TwoPairs
elif snd tallyBuffer.[1] = 3 then Hand.Three
elif snd tallyBuffer.[1] = 2 then Hand.TwoPairs
else Hand.Three
elif tallyBuffer.Count = 4 then
Hand.Pair
else
Hand.High
toInt hand contents
type RankedHandAndBid = uint32
[<Literal>]
let bidSeparator = 1001ul
let inline toRankedHandAndBid (r : RankedHand) (bid : uint32) : RankedHandAndBid = bidSeparator * r + bid
let inline getBid (r : RankedHandAndBid) : uint32 = uint32 (r % bidSeparator)
let parse (adjustJoker : bool) (s : string) : ResizeArray<RankedHandAndBid> =
use mutable lines = StringSplitEnumerator.make '\n' s
let result = ResizeArray.create 4
let tallies = ResizeArray.create 5
while lines.MoveNext () do
if not lines.Current.IsEmpty then
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
line.MoveNext () |> ignore
let rankedHand = parseHand tallies adjustJoker line.Current
line.MoveNext () |> ignore
let bid =
UInt32.Parse (line.Current, NumberStyles.Integer, CultureInfo.InvariantCulture)
result.Add (toRankedHandAndBid rankedHand bid)
result
let part1 (s : string) =
let arr = parse false s
arr.Sort ()
let mutable answer = 0ul
for i = 0 to arr.Count - 1 do
answer <- answer + getBid arr.[i] * (uint32 i + 1ul)
answer
let part2 (s : string) =
let arr = parse true s
arr.Sort ()
let mutable answer = 0ul
for i = 0 to arr.Count - 1 do
answer <- answer + getBid arr.[i] * (uint32 i + 1ul)
answer

View File

@@ -0,0 +1,110 @@
namespace AdventOfCode2023
open System
open System.Collections.Generic
[<RequireQualifiedAccess>]
module Day8 =
type Instructions =
{
/// "true" is 'R'
Steps : bool array
Nodes : Dictionary<string, string * string>
}
let parse (s : string) =
use mutable lines = StringSplitEnumerator.make '\n' s
lines.MoveNext () |> ignore
let stepsLine = lines.Current.TrimEnd ()
let steps = Array.zeroCreate stepsLine.Length
for i = 0 to stepsLine.Length - 1 do
steps.[i] <- (stepsLine.[i] = 'R')
let dict = Dictionary ()
while lines.MoveNext () do
if not lines.Current.IsEmpty then
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
line.MoveNext () |> ignore
let key = line.Current.ToString ()
line.MoveNext () |> ignore
line.MoveNext () |> ignore
let v1 = line.Current.Slice(1, line.Current.Length - 2).ToString ()
line.MoveNext () |> ignore
let v2 = line.Current.Slice(0, line.Current.Length - 1).ToString ()
dict.[key] <- (v1, v2)
{
Steps = steps
Nodes = dict
}
let part1 (s : string) =
let data = parse s
let mutable i = 0
let mutable currentNode = "AAA"
let mutable answer = 0
while currentNode <> "ZZZ" do
let instruction = data.Nodes.[currentNode]
if data.Steps.[i] then
// "true" is R
currentNode <- snd instruction
else
currentNode <- fst instruction
i <- (i + 1) % data.Steps.Length
answer <- answer + 1
answer
let inline lcm (periods : ^T[]) =
let mutable lcm = periods.[0]
let mutable i = 1
while i < periods.Length do
let euclid = Arithmetic.euclideanAlgorithm lcm periods.[i]
lcm <- (lcm * periods.[i]) / euclid.Hcf
i <- i + 1
lcm
let part2 (s : string) =
let data = parse s
let startingNodes = ResizeArray ()
for key in data.Nodes.Keys do
if key.[key.Length - 1] = 'A' then
startingNodes.Add key
let periods =
Array.init
startingNodes.Count
(fun startNode ->
let mutable i = 0
let mutable currentNode = startingNodes.[startNode]
let mutable answer = 0ul
while currentNode.[currentNode.Length - 1] <> 'Z' do
let instruction = data.Nodes.[currentNode]
if data.Steps.[i] then
// "true" is R
currentNode <- snd instruction
else
currentNode <- fst instruction
i <- (i + 1) % data.Steps.Length
answer <- answer + 1ul
uint64 answer
)
lcm periods

View File

@@ -0,0 +1,109 @@
namespace AdventOfCode2023
open System
open System.Globalization
open System.Runtime.CompilerServices
type EfficientString = System.ReadOnlySpan<char>
[<RequireQualifiedAccess>]
module EfficientString =
let inline isEmpty (s : EfficientString) : bool = s.IsEmpty
let inline ofString (s : string) : EfficientString = s.AsSpan ()
let inline toString (s : EfficientString) : string = s.ToString ()
let inline trimStart (s : EfficientString) : EfficientString = s.TrimStart ()
let inline slice (start : int) (length : int) (s : EfficientString) : EfficientString = s.Slice (start, length)
let inline equals (a : string) (other : EfficientString) : bool =
MemoryExtensions.Equals (other, a.AsSpan (), StringComparison.Ordinal)
/// Mutates the input to drop up to the first instance of the input char,
/// and returns what was dropped.
/// If the char is not present, deletes the input.
let takeUntil<'a> (c : char) (s : EfficientString byref) : EfficientString =
let first = s.IndexOf c
if first < 0 then
let toRet = s
s <- EfficientString.Empty
toRet
else
let toRet = slice 0 first s
s <- slice (first + 1) (s.Length - first - 1) s
toRet
[<Struct>]
[<IsByRefLike>]
type StringSplitEnumerator =
internal
{
Original : EfficientString
mutable Remaining : EfficientString
mutable InternalCurrent : EfficientString
SplitOn : char
}
interface IDisposable with
member this.Dispose () = ()
member this.Current : EfficientString = this.InternalCurrent
member this.MoveNext () =
if this.Remaining.Length = 0 then
false
else
this.InternalCurrent <- EfficientString.takeUntil this.SplitOn &this.Remaining
true
member this.GetEnumerator () = this
[<RequireQualifiedAccess>]
module StringSplitEnumerator =
let make (splitChar : char) (s : string) : StringSplitEnumerator =
{
Original = EfficientString.ofString s
Remaining = EfficientString.ofString s
InternalCurrent = EfficientString.Empty
SplitOn = splitChar
}
let make' (splitChar : char) (s : ReadOnlySpan<char>) : StringSplitEnumerator =
{
Original = s
Remaining = s
InternalCurrent = EfficientString.Empty
SplitOn = splitChar
}
let chomp (s : string) (e : byref<StringSplitEnumerator>) : unit =
#if DEBUG
if not (e.MoveNext ()) || not (EfficientString.equals s e.Current) then
failwithf "expected '%s', got '%s'" s (e.Current.ToString ())
#else
e.MoveNext () |> ignore
#endif
let consumeInt (e : byref<StringSplitEnumerator>) : int =
if not (e.MoveNext ()) then
failwith "expected an int, got nothing"
Int32.Parse e.Current
let consumeU32 (e : byref<StringSplitEnumerator>) : uint32 =
if not (e.MoveNext ()) then
failwith "expected an int, got nothing"
UInt32.Parse e.Current
let consumeU64 (e : byref<StringSplitEnumerator>) : uint64 =
if not (e.MoveNext ()) then
failwith "expected an int, got nothing"
UInt64.Parse e.Current

View File

@@ -0,0 +1,40 @@
namespace AdventOfCode2023.ResizeArray
open System
type ResizeArray<'T> =
private
{
mutable Array : 'T array
mutable Length : int
}
member this.Count = this.Length
member this.Clear () = this.Length <- 0
member this.Add (t : 'T) =
if this.Length < this.Array.Length then
this.Array.[this.Length] <- t
else
let newLength = this.Length * 2
let newArray = Array.zeroCreate<'T> newLength
Array.blit this.Array 0 newArray 0 this.Length
newArray.[this.Length] <- t
this.Array <- newArray
this.Length <- this.Length + 1
member this.Item
with get (i : int) = this.Array.[i]
and set (i : int) (t : 'T) = this.Array.[i] <- t
member this.Sort () =
Span(this.Array).Slice(0, this.Count).Sort ()
[<RequireQualifiedAccess>]
module ResizeArray =
let create<'T> (capacity : int) =
{
Array = Array.zeroCreate<'T> capacity
Length = 0
}

View File

@@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AdventOfCode2023.FSharp", "AdventOfCode2023.FSharp\AdventOfCode2023.FSharp.fsproj", "{E2EC7715-E2C9-4671-AFBD-84D740B604FE}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "Test\Test.fsproj", "{AC9C7858-2F5D-4DE1-8E13-0A87E1EA8598}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AdventOfCode2023.FSharp.Lib", "AdventOfCode2023.FSharp.Lib\AdventOfCode2023.FSharp.Lib.fsproj", "{95CE0568-3D1A-4060-BB54-52460FB1E399}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E2EC7715-E2C9-4671-AFBD-84D740B604FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2EC7715-E2C9-4671-AFBD-84D740B604FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2EC7715-E2C9-4671-AFBD-84D740B604FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2EC7715-E2C9-4671-AFBD-84D740B604FE}.Release|Any CPU.Build.0 = Release|Any CPU
{AC9C7858-2F5D-4DE1-8E13-0A87E1EA8598}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC9C7858-2F5D-4DE1-8E13-0A87E1EA8598}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC9C7858-2F5D-4DE1-8E13-0A87E1EA8598}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC9C7858-2F5D-4DE1-8E13-0A87E1EA8598}.Release|Any CPU.Build.0 = Release|Any CPU
{95CE0568-3D1A-4060-BB54-52460FB1E399}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95CE0568-3D1A-4060-BB54-52460FB1E399}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95CE0568-3D1A-4060-BB54-52460FB1E399}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95CE0568-3D1A-4060-BB54-52460FB1E399}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AdventOfCode2023.FSharp.Lib\AdventOfCode2023.FSharp.Lib.fsproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,189 @@
namespace AdventOfCode2023
#if DEBUG
#else
#nowarn "9"
#endif
open System
open System.Diagnostics
open System.IO
module Program =
let inline toUs (ticks : int64) =
1_000_000.0 * float ticks / float Stopwatch.Frequency
[<EntryPoint>]
let main argv =
let endToEnd = Stopwatch.StartNew ()
endToEnd.Restart ()
let dir = DirectoryInfo argv.[0]
let sw = Stopwatch.StartNew ()
Console.WriteLine "=====Day 1====="
do
sw.Restart ()
let input =
try
Path.Combine (dir.FullName, "day1part1.txt") |> File.ReadAllText
with :? FileNotFoundException ->
Path.Combine (dir.FullName, "day1.txt") |> File.ReadAllText
let part1 = Day1.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let input = Path.Combine (dir.FullName, "day1.txt") |> File.ReadAllText
let part2 = Day1.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 2====="
do
let input = Path.Combine (dir.FullName, "day2.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day2.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day2.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 3====="
do
let input = Path.Combine (dir.FullName, "day3.txt") |> File.ReadAllBytes
sw.Restart ()
let resultArr, len, lineCount = Day3.parse input
sw.Stop ()
Console.Error.WriteLine (
(1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString ()
+ "ms parse"
)
#if DEBUG
let contents =
{
Elements = Array.take len resultArr
Width = len / lineCount
}
#else
use ptr = fixed resultArr
let contents =
{
Elements = ptr
Length = len
Width = len / lineCount
}
#endif
let part1 = Day3.part1 contents
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day3.part2 contents
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 4====="
do
let input = Path.Combine (dir.FullName, "day4.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day4.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day4.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 5====="
do
let input = Path.Combine (dir.FullName, "day5.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day5.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day5.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 6====="
do
let input = Path.Combine (dir.FullName, "day6.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day6.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day6.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 7====="
do
let input = Path.Combine (dir.FullName, "day7.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day7.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let part2 = Day7.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
Console.WriteLine "=====Day 8====="
do
let input =
try
Path.Combine (dir.FullName, "day8part1.txt") |> File.ReadAllText
with :? FileNotFoundException ->
Path.Combine (dir.FullName, "day8.txt") |> File.ReadAllText
sw.Restart ()
let part1 = Day8.part1 input
sw.Stop ()
Console.WriteLine (part1.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
sw.Restart ()
let input = Path.Combine (dir.FullName, "day8.txt") |> File.ReadAllText
let part2 = Day8.part2 input
sw.Stop ()
Console.WriteLine (part2.ToString ())
Console.Error.WriteLine ((1_000.0 * float sw.ElapsedTicks / float Stopwatch.Frequency).ToString () + "ms")
endToEnd.Stop ()
Console.Error.WriteLine (
(1_000.0 * float endToEnd.ElapsedTicks / float Stopwatch.Frequency).ToString ()
+ "ms total"
)
0

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Compile Include="Util.fs" />
<Compile Include="TestDay1.fs" />
<Compile Include="TestDay2.fs" />
<Compile Include="TestDay3.fs" />
<Compile Include="TestDay4.fs" />
<Compile Include="TestDay5.fs" />
<Compile Include="TestDay6.fs" />
<Compile Include="TestDay7.fs" />
<Compile Include="TestDay8.fs" />
<EmbeddedResource Include="samples\day1.txt" />
<EmbeddedResource Include="samples\day1part1.txt" />
<EmbeddedResource Include="samples\day2.txt" />
<EmbeddedResource Include="samples\day3.txt" />
<EmbeddedResource Include="samples\day4.txt" />
<EmbeddedResource Include="samples\day5.txt" />
<EmbeddedResource Include="samples\day6.txt" />
<EmbeddedResource Include="samples\day7.txt" />
<EmbeddedResource Include="samples\day8part1.txt" />
<EmbeddedResource Include="samples\day8.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FsUnit" Version="5.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AdventOfCode2023.FSharp.Lib\AdventOfCode2023.FSharp.Lib.fsproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,47 @@
namespace AdventOfCode2023.Test
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay1 =
let sample1 = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day1part1.txt"
[<Test>]
let part1Sample () =
sample1 |> Day1.part1 |> shouldEqual 142
let sample2 = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day1.txt"
[<Test>]
let part2Sample () =
sample2 |> Day1.part2 |> shouldEqual 281
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day1.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day1.part1 s |> shouldEqual 54304
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day1.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day1.part2 s |> shouldEqual 54418

View File

@@ -0,0 +1,44 @@
namespace AdventOfCode2023.Test
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay2 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day2.txt"
[<Test>]
let part1Sample () = sample |> Day2.part1 |> shouldEqual 8
[<Test>]
let part2Sample () =
sample |> Day2.part2 |> shouldEqual 2286
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day2.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day2.part1 s |> shouldEqual 2727
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day2.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day2.part2 s |> shouldEqual 56580

View File

@@ -0,0 +1,126 @@
namespace AdventOfCode2023.Test
#if DEBUG
#else
#nowarn "9"
#endif
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay3 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day3.txt"
[<Test>]
let part1Sample () =
let arr, len, rows = sample.ToCharArray () |> Array.map byte |> Day3.parse
#if DEBUG
let arr =
{
Elements = arr
Width = len / rows
}
#else
use arr = fixed arr
let arr =
{
Elements = arr
Length = len
Width = len / rows
}
#endif
arr |> Day3.part1 |> shouldEqual 4361
[<Test>]
let part2Sample () =
let arr, len, rows = sample.ToCharArray () |> Array.map byte |> Day3.parse
#if DEBUG
let arr =
{
Elements = arr
Width = len / rows
}
#else
use arr = fixed arr
let arr =
{
Elements = arr
Length = len
Width = len / rows
}
#endif
arr |> Day3.part2 |> shouldEqual 467835
[<Test>]
let part1Actual () =
let bytes =
try
File.ReadAllBytes (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day3.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
let arr, len, rows = Day3.parse bytes
#if DEBUG
let arr =
{
Elements = arr
Width = len / rows
}
#else
use arr = fixed arr
let arr =
{
Elements = arr
Length = len
Width = len / rows
}
#endif
Day3.part1 arr |> shouldEqual 540131
[<Test>]
let part2Actual () =
let bytes =
try
File.ReadAllBytes (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day3.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
let arr, len, rows = Day3.parse bytes
#if DEBUG
let arr =
{
Elements = arr
Width = len / rows
}
#else
use arr = fixed arr
let arr =
{
Elements = arr
Length = len
Width = len / rows
}
#endif
Day3.part2 arr |> shouldEqual 86879020

View File

@@ -0,0 +1,43 @@
namespace AdventOfCode2023.Test
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay4 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day4.txt"
[<Test>]
let part1Sample () = sample |> Day4.part1 |> shouldEqual 13
[<Test>]
let part2Sample () = sample |> Day4.part2 |> shouldEqual 30
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day4.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day4.part1 s |> shouldEqual 27454
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day4.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day4.part2 s |> shouldEqual 6857330

View File

@@ -0,0 +1,46 @@
namespace AdventOfCode2023.Test
open System
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay5 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day5.txt"
[<Test>]
let part1Sample () =
sample |> Day5.part1 |> shouldEqual 35ul
[<Test>]
let part2Sample () =
sample |> Day5.part2 |> shouldEqual 46ul
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day5.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day5.part1 s |> shouldEqual 806029445ul
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day5.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day5.part2 s |> shouldEqual 59370572ul

View File

@@ -0,0 +1,46 @@
namespace AdventOfCode2023.Test
open System
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay6 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day6.txt"
[<Test>]
let part1Sample () =
sample |> Day6.part1 |> shouldEqual 288uL
[<Test>]
let part2Sample () =
sample |> Day6.part2 |> shouldEqual 71503uL
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day6.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day6.part1 s |> shouldEqual 32076uL
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day6.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day6.part2 s |> shouldEqual 34278221uL

View File

@@ -0,0 +1,46 @@
namespace AdventOfCode2023.Test
open System
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay7 =
let sample = Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day7.txt"
[<Test>]
let part1Sample () =
sample |> Day7.part1 |> shouldEqual 6440ul
[<Test>]
let part2Sample () =
sample |> Day7.part2 |> shouldEqual 5905ul
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day7.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day7.part1 s |> shouldEqual 250058342ul
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day7.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day7.part2 s |> shouldEqual 250506580ul

View File

@@ -0,0 +1,57 @@
namespace AdventOfCode2023.Test
open AdventOfCode2023
open NUnit.Framework
open FsUnitTyped
open System.IO
[<TestFixture>]
module TestDay8 =
[<Test>]
let part1Sample () =
Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day8part1.txt"
|> Day8.part1
|> shouldEqual 2
[<Test>]
let part1Sample2 () =
"""LLR
AAA = (BBB, BBB)
BBB = (AAA, ZZZ)
ZZZ = (ZZZ, ZZZ)"""
|> Day8.part1
|> shouldEqual 6
[<Test>]
let part2Sample () =
Assembly.getEmbeddedResource typeof<Dummy>.Assembly "day8.txt"
|> Day8.part2
|> shouldEqual 6uL
[<Test>]
let part1Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day8.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day8.part1 s |> shouldEqual 19199
[<Test>]
let part2Actual () =
let s =
try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day8.txt"))
with
| :? DirectoryNotFoundException
| :? FileNotFoundException ->
Assert.Inconclusive ()
failwith "unreachable"
Day8.part2 s |> shouldEqual 13663968099527uL

View File

@@ -0,0 +1,22 @@
namespace AdventOfCode2023.Test
open System.IO
open System.Reflection
type Dummy =
class
end
[<RequireQualifiedAccess>]
module Assembly =
let getEmbeddedResource (assembly : Assembly) (name : string) : string =
let names = assembly.GetManifestResourceNames ()
let names = names |> Seq.filter (fun s -> s.EndsWith name)
use s =
names
|> Seq.exactlyOne
|> assembly.GetManifestResourceStream
|> fun s -> new StreamReader (s)
s.ReadToEnd ()

View File

@@ -0,0 +1,7 @@
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen

View File

@@ -0,0 +1,4 @@
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet

View File

@@ -0,0 +1,5 @@
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green

View File

@@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

View File

@@ -0,0 +1,6 @@
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11

View File

@@ -0,0 +1,33 @@
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4

View File

@@ -0,0 +1,2 @@
Time: 7 15 30
Distance: 9 40 200

View File

@@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483

View File

@@ -0,0 +1,10 @@
LR
11A = (11B, XXX)
11B = (XXX, 11Z)
11Z = (11B, XXX)
22A = (22B, XXX)
22B = (22C, 22C)
22C = (22Z, 22Z)
22Z = (22B, 22B)
XXX = (XXX, XXX)

View File

@@ -0,0 +1,9 @@
RL
AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Patrick Stevens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

33
README.md Normal file
View File

@@ -0,0 +1,33 @@
# Advent of Code 2023
[Puzzle site](https://adventofcode.com/2023).
# Speed
Ahead-of-time compiled with `PublishAot`, M1 Max.
The format is: "answer part1\ntime\nanswer part2\ntime\n...", with possible extra lines indicating how long it took to parse the input if I happen to have split that out.
After day 3:
```
54304
0.549458ms
54418
0.710375ms
2727
0.119959ms
56580
0.155708ms
0.1395ms parse
540131
0.1395ms
86879020
0.840791ms
4.144166ms total
```
# Building yourself
Note that `PublishAot` assumes a lot of stuff about your environment, which is not necessarily true.
The given flake should allow you to complete the publish except for a linking stage at the end: the publish will print out a failed command line, and you'll have to strip out some `-o` flags from it and run it manually.
Then run `dotnet publish` again and it should succeed.

82
flake.lock generated Normal file
View File

@@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nix-ld": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1701153607,
"narHash": "sha256-h+odOVyiGmEERMECoFOj5P7FPiMR8IPRzroFA4sKivg=",
"owner": "Mic92",
"repo": "nix-ld",
"rev": "bf5aa84a713c31d95b4307e442e966d6c7fd7ae7",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "nix-ld",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1701253981,
"narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nix-ld": "nix-ld",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

54
flake.nix Normal file
View File

@@ -0,0 +1,54 @@
{
description = "Advent of Code 2023";
inputs = {
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-ld = {
url = "github:Mic92/nix-ld";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
flake-utils,
nix-ld,
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
in
# Conditionally include Swift and Apple SDK for Darwin systems
let
darwinDeps =
if system == "x86_64-darwin" || system == "aarch64-darwin"
then [
pkgs.swift
pkgs.darwin.apple_sdk.frameworks.Foundation
pkgs.darwin.apple_sdk.frameworks.CryptoKit
pkgs.darwin.apple_sdk.frameworks.GSS
]
else [];
in let
deps = darwinDeps ++ [pkgs.zlib pkgs.zlib.dev pkgs.openssl pkgs.icu];
in {
devShells = {
default = pkgs.mkShell {
HOME = "/tmp/dotnet-home";
NUGET_PACKAGES = "/tmp/dotnet-home/.nuget/packages";
LINKER_PATH = "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
buildInputs = with pkgs;
[
(with dotnetCorePackages;
combinePackages [
dotnet-sdk_8
dotnetPackages.Nuget
])
]
++ [pkgs.alejandra pkgs.patchelf pkgs.strace];
};
};
}
);
}