First code dump

This commit is contained in:
Smaug123
2021-01-14 19:34:21 +00:00
parent 471b7ae944
commit 53af1fb37d
8 changed files with 345 additions and 0 deletions

25
.github/workflows/dotnet-core.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: .NET Core
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
- name: Install dependencies
run: dotnet restore Expression.sln
- name: Build
run: dotnet build Expression.sln --configuration Release --no-restore
- name: Test
run: dotnet test Expression.sln --no-restore --verbosity normal

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea/
.ionide/
AdventOfCode.sln.DotSettings.user

22
Expression.sln Normal file
View File

@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Expression", "Expression\Expression.fsproj", "{5B7A23ED-C981-4C7D-87D5-40C0C0AD377D}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test", "Test\Test.fsproj", "{15040E44-D6F4-43DE-9830-A43CB46715DA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5B7A23ED-C981-4C7D-87D5-40C0C0AD377D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B7A23ED-C981-4C7D-87D5-40C0C0AD377D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B7A23ED-C981-4C7D-87D5-40C0C0AD377D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B7A23ED-C981-4C7D-87D5-40C0C0AD377D}.Release|Any CPU.Build.0 = Release|Any CPU
{15040E44-D6F4-43DE-9830-A43CB46715DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15040E44-D6F4-43DE-9830-A43CB46715DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15040E44-D6F4-43DE-9830-A43CB46715DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15040E44-D6F4-43DE-9830-A43CB46715DA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

179
Expression/Expression.fs Normal file
View File

@@ -0,0 +1,179 @@
namespace Expression
open Expression.Internals
type BinaryOperation =
| Plus
| Minus
| Times
| Divide
type Expr =
| Variable of char
| Const of int
| BinaryOperation of BinaryOperation * Expr * Expr
type Token =
| BinaryOperation of BinaryOperation
| Variable of char
| Const of int
[<RequireQualifiedAccess>]
module Expr =
[<RequireQualifiedAccess>]
module Seq =
let tokenise (input : string) : Token seq =
seq {
let mutable i = 0
let mutable currentInt = None
while i < input.Length do
match currentInt with
| None ->
match input.[i] with
| '+' -> yield Token.BinaryOperation BinaryOperation.Plus
| '*' -> yield Token.BinaryOperation BinaryOperation.Times
| '-' -> yield Token.BinaryOperation BinaryOperation.Minus
| '/' -> yield Token.BinaryOperation BinaryOperation.Divide
| ' ' -> ()
| Int j ->
currentInt <- Some j
| x -> yield Token.Variable x
i <- i + 1
| Some j ->
match input.[i] with
| Int k ->
currentInt <- Some (10 * j + k)
i <- i + 1
| _ ->
yield Token.Const j
currentInt <- None
match currentInt with
| None -> ()
| Some j -> yield Token.Const j
}
let make (t : Token seq) : Expr =
use e = t.GetEnumerator ()
let mutable opStack : (BinaryOperation * Expr option) list = []
let mutable answer = None
let rec addArg (expr : Expr) =
match opStack with
| [] ->
match answer with
| None -> answer <- Some expr
| Some _ -> failwith "imbalance"
| (op, None) :: rest ->
opStack <- (op, Some expr) :: rest
| (op, Some x) :: rest ->
opStack <- rest
addArg (Expr.BinaryOperation (op, x, expr))
while e.MoveNext () do
match e.Current with
| Token.Const i ->
addArg (Expr.Const i)
| Token.Variable x ->
addArg (Expr.Variable x)
| Token.BinaryOperation op ->
opStack <- (op, None) :: opStack
match answer with
| None -> failwith "still waiting"
| Some a -> a
let tokenise (input : string) : Token list =
let rec go (currentInt : int option) (acc : Token list) (i : int) =
match currentInt with
| None ->
if i >= input.Length then acc else
match input.[i] with
| '+' -> go None (Token.BinaryOperation BinaryOperation.Plus :: acc) (i + 1)
| '*' -> go None (Token.BinaryOperation BinaryOperation.Times :: acc) (i + 1)
| '-' -> go None (Token.BinaryOperation BinaryOperation.Minus :: acc) (i + 1)
| '/' -> go None (Token.BinaryOperation BinaryOperation.Divide :: acc) (i + 1)
| ' ' -> go None acc (i + 1)
| Int j -> go (Some j) acc (i + 1)
| x -> go None (Token.Variable x :: acc) (i + 1)
| Some j ->
if i >= input.Length then Token.Const j :: acc else
match input.[i] with
| Int k -> go (Some (10 * j + k)) acc (i + 1)
| _ -> go None (Token.Const j :: acc) i
go None [] 0
|> List.rev
let make (t : Token list) : Expr =
let rec go (t : Token list) : Expr * Token list =
match t with
| [] -> failwith "Received an empty token list!"
| Token.BinaryOperation op :: rest ->
let expr1, rest = go rest
let expr2, rest = go rest
Expr.BinaryOperation (op, expr1, expr2), rest
| Token.Const i :: rest -> Expr.Const i, rest
| Token.Variable x :: rest -> Expr.Variable x, rest
let expr, rest = go t
if not rest.IsEmpty then failwith "Oh no!"
expr
let rec eval (variables : Map<char, int Set>) (e : Expr) : int Set =
match e with
| Expr.Const i -> Set.singleton i
| Expr.Variable x -> variables.[x]
| Expr.BinaryOperation (op, e1, e2) ->
let s1 = eval variables e1
let s2 = eval variables e2
Seq.allPairs s1 s2
|> Seq.choose (fun (a, b) ->
match op with
| Plus -> Some (a + b)
| Times -> Some (a * b)
| Divide -> if b = 0 then None else Some (a / b)
| Minus -> Some (a - b)
)
|> Set.ofSeq
let naiveMax (variables : Map<char, int Set>) (e : Expr) : int =
eval variables e
|> Set.maxElement
let mapUnion (resolve : 'v -> 'v -> 'v) (m1 : Map<'k, 'v>) (m2 : Map<'k, 'v>) : Map<'k, 'v> =
m1
|> Map.map (fun k v1 ->
match Map.tryFind k m2 with
| None -> v1
| Some v2 -> resolve v1 v2
)
let rec counts (e : Expr) : Map<char, int> =
match e with
| Expr.BinaryOperation (_, e1, e2) ->
mapUnion (+) (counts e1) (counts e2)
| Expr.Const _ -> Map.empty
| Expr.Variable x -> Map.ofList [x, 1]
let max (variables : Map<char, int * int>) (e : Expr) : int =
// If a variable appears only once in the expression, then the maximum will be attained at an extreme or at 0 or +-1.
// I can't be bothered to think too carefully about that, so just try them all.
let singleOccurrences =
counts e
|> Map.toSeq
|> Seq.choose (fun (k, v) -> if v = 1 then Some k else None)
|> Set.ofSeq
let variables =
variables
|> Map.map (fun k (min, max) ->
if Set.contains k singleOccurrences then
if min < 0 && max > 0 then Set.ofList [min; -1; 0; 1; max]
elif min < 0 && max = 0 then Set.ofList [min; -1; 0]
elif min < 0 && max < 0 then Set.ofList [min; max]
elif min = 0 then Set.ofList [0; 1; max]
else Set.ofList [min ; max]
else Set.ofList [min..max]
)
naiveMax variables e

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Utils.fs" />
<Compile Include="Expression.fs" />
</ItemGroup>
</Project>

8
Expression/Utils.fs Normal file
View File

@@ -0,0 +1,8 @@
namespace Expression.Internals
[<AutoOpen>]
module ActivePatterns =
let (|Int|_|) (c : char) : int option =
if '0' <= c && c <= '9' then
Some (int(c) - int('0'))
else None

22
Test/Test.fsproj Normal file
View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FsUnit" Version="4.0.2" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="TestExpression.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Expression\Expression.fsproj" />
</ItemGroup>
</Project>

69
Test/TestExpression.fs Normal file
View File

@@ -0,0 +1,69 @@
namespace Expression.Test
open NUnit.Framework
open FsUnitTyped
open Expression
[<TestFixture>]
module TestExpression =
[<Test>]
let ``Test tokenise`` () =
Expr.tokenise "* + 2 x y"
|> Expr.make
|> shouldEqual (Expr.BinaryOperation (BinaryOperation.Times, Expr.BinaryOperation (BinaryOperation.Plus, Expr.Const 2, Expr.Variable 'x'), Expr.Variable 'y'))
Expr.tokenise "+ 6 * - 4 + 2 3 8"
|> Expr.make
|> shouldEqual (Expr.BinaryOperation (BinaryOperation.Plus, Expr.Const 6, Expr.BinaryOperation (Times, Expr.BinaryOperation (Minus, Expr.Const 4, Expr.BinaryOperation (Plus, Expr.Const 2, Expr.Const 3)), Expr.Const 8)))
[<Test>]
let ``Test Seq.tokenise`` () =
Expr.Seq.tokenise "* + 2 x y"
|> Expr.Seq.make
|> shouldEqual (Expr.BinaryOperation (BinaryOperation.Times, Expr.BinaryOperation (BinaryOperation.Plus, Expr.Const 2, Expr.Variable 'x'), Expr.Variable 'y'))
Expr.Seq.tokenise "+ 6 * - 4 + 2 3 8"
|> Expr.Seq.make
|> shouldEqual (Expr.BinaryOperation (BinaryOperation.Plus, Expr.Const 6, Expr.BinaryOperation (Times, Expr.BinaryOperation (Minus, Expr.Const 4, Expr.BinaryOperation (Plus, Expr.Const 2, Expr.Const 3)), Expr.Const 8)))
[<Test>]
let testEval () =
Expr.tokenise "* + 2 x y"
|> Expr.make
|> Expr.eval (Map.ofList ['x', Set.ofList [0 ; 1] ; 'y', Set.ofList [2 ; 3]])
|> shouldEqual (Set.ofList [for x in 0..1 do for y in 2..3 do yield (2+x)*y])
Expr.tokenise "- 0 10"
|> Expr.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton -10)
Expr.tokenise "+ 6 * - 4 + 2 3 8"
|> Expr.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton -2)
Expr.tokenise "+*3 2 1"
|> Expr.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton 7)
[<Test>]
let testEvalSeq () =
Expr.Seq.tokenise "* + 2 x y"
|> Expr.Seq.make
|> Expr.eval (Map.ofList ['x', Set.ofList [0 ; 1] ; 'y', Set.ofList [2 ; 3]])
|> shouldEqual (Set.ofList [for x in 0..1 do for y in 2..3 do yield (2+x)*y])
Expr.Seq.tokenise "- 0 10"
|> Expr.Seq.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton -10)
Expr.Seq.tokenise "+ 6 * - 4 + 2 3 8"
|> Expr.Seq.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton -2)
Expr.Seq.tokenise "+*3 2 1"
|> Expr.Seq.make
|> Expr.eval Map.empty
|> shouldEqual (Set.singleton 7)