From 53af1fb37dfe7dad8edba64cf5a7494adc0f6710 Mon Sep 17 00:00:00 2001 From: Smaug123 Date: Thu, 14 Jan 2021 19:34:21 +0000 Subject: [PATCH] First code dump --- .github/workflows/dotnet-core.yml | 25 +++++ .gitignore | 8 ++ Expression.sln | 22 ++++ Expression/Expression.fs | 179 ++++++++++++++++++++++++++++++ Expression/Expression.fsproj | 12 ++ Expression/Utils.fs | 8 ++ Test/Test.fsproj | 22 ++++ Test/TestExpression.fs | 69 ++++++++++++ 8 files changed, 345 insertions(+) create mode 100644 .github/workflows/dotnet-core.yml create mode 100644 .gitignore create mode 100644 Expression.sln create mode 100644 Expression/Expression.fs create mode 100644 Expression/Expression.fsproj create mode 100644 Expression/Utils.fs create mode 100644 Test/Test.fsproj create mode 100644 Test/TestExpression.fs diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml new file mode 100644 index 0000000..a89f92e --- /dev/null +++ b/.github/workflows/dotnet-core.yml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8431f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea/ +.ionide/ +AdventOfCode.sln.DotSettings.user diff --git a/Expression.sln b/Expression.sln new file mode 100644 index 0000000..13da001 --- /dev/null +++ b/Expression.sln @@ -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 diff --git a/Expression/Expression.fs b/Expression/Expression.fs new file mode 100644 index 0000000..641f75f --- /dev/null +++ b/Expression/Expression.fs @@ -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 + +[] +module Expr = + [] + 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) (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) (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 = + 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) (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 + diff --git a/Expression/Expression.fsproj b/Expression/Expression.fsproj new file mode 100644 index 0000000..0467a1c --- /dev/null +++ b/Expression/Expression.fsproj @@ -0,0 +1,12 @@ + + + + net5.0 + + + + + + + + diff --git a/Expression/Utils.fs b/Expression/Utils.fs new file mode 100644 index 0000000..7ef4b32 --- /dev/null +++ b/Expression/Utils.fs @@ -0,0 +1,8 @@ +namespace Expression.Internals + +[] +module ActivePatterns = + let (|Int|_|) (c : char) : int option = + if '0' <= c && c <= '9' then + Some (int(c) - int('0')) + else None diff --git a/Test/Test.fsproj b/Test/Test.fsproj new file mode 100644 index 0000000..eeff6c5 --- /dev/null +++ b/Test/Test.fsproj @@ -0,0 +1,22 @@ + + + + net5.0 + + + + + + + + + + + + + + + + + + diff --git a/Test/TestExpression.fs b/Test/TestExpression.fs new file mode 100644 index 0000000..c2f2196 --- /dev/null +++ b/Test/TestExpression.fs @@ -0,0 +1,69 @@ +namespace Expression.Test + +open NUnit.Framework +open FsUnitTyped +open Expression + +[] +module TestExpression = + [] + 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))) + + [] + 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))) + + [] + 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) + + [] + 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)