diff --git a/config.json b/config.json index dcffe3ed5..7a614ea8d 100644 --- a/config.json +++ b/config.json @@ -2233,6 +2233,20 @@ "result" ], "difficulty": 5 + }, + { + "slug": "satellite", + "name": "Satellite", + "uuid": "bcfeb5b7-08d3-4039-95b2-ebda6b7288aa", + "practices": [ + "recursion" + ], + "prerequisites": [ + "recursion", + "discriminated-unions", + "numbers" + ], + "difficulty": 7 } ], "foregone": [ diff --git a/exercises/Exercises.sln b/exercises/Exercises.sln index 40399b4bf..9f18c1eb2 100644 --- a/exercises/Exercises.sln +++ b/exercises/Exercises.sln @@ -309,6 +309,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "KillerSudokuHelper", "pract EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "StateOfTicTacToe", "practice\state-of-tic-tac-toe\StateOfTicTacToe.fsproj", "{A12FEF19-5EE8-430E-BD66-2D93ADFC1944}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Satellite", "practice\satellite\Satellite.fsproj", "{FF432193-3D08-4BD4-ADCD-1512BF67A425}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -910,6 +912,10 @@ Global {A12FEF19-5EE8-430E-BD66-2D93ADFC1944}.Debug|Any CPU.Build.0 = Debug|Any CPU {A12FEF19-5EE8-430E-BD66-2D93ADFC1944}.Release|Any CPU.ActiveCfg = Release|Any CPU {A12FEF19-5EE8-430E-BD66-2D93ADFC1944}.Release|Any CPU.Build.0 = Release|Any CPU + {FF432193-3D08-4BD4-ADCD-1512BF67A425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF432193-3D08-4BD4-ADCD-1512BF67A425}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF432193-3D08-4BD4-ADCD-1512BF67A425}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF432193-3D08-4BD4-ADCD-1512BF67A425}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B404AA3C-A226-409A-A035-6C1DC66940DD} = {B7E719DB-FB8D-43B4-B529-55FCF6E3DC3F} @@ -1063,5 +1069,6 @@ Global {1850FAE9-5ACB-41D0-91BB-AD17A1021248} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} {FCE9E627-CFF9-4EF3-84BE-D42B354825AA} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} {A12FEF19-5EE8-430E-BD66-2D93ADFC1944} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} + {FF432193-3D08-4BD4-ADCD-1512BF67A425} = {391BEEC4-91A8-43F3-AE94-D5CB9A8FA611} EndGlobalSection EndGlobal diff --git a/exercises/practice/satellite/.config/dotnet-tools.json b/exercises/practice/satellite/.config/dotnet-tools.json new file mode 100644 index 000000000..0f7926bad --- /dev/null +++ b/exercises/practice/satellite/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas-tool": { + "version": "4.7.9", + "commands": [ + "fantomas" + ] + } + } +} \ No newline at end of file diff --git a/exercises/practice/satellite/.docs/instructions.md b/exercises/practice/satellite/.docs/instructions.md new file mode 100644 index 000000000..fbbf14f43 --- /dev/null +++ b/exercises/practice/satellite/.docs/instructions.md @@ -0,0 +1,27 @@ +# Instructions + +Imagine you need to transmit a binary tree to a satellite approaching Alpha Centauri and you have limited bandwidth. +Since the tree has no repeating items it can be uniquely represented by its [pre-order and in-order traversals][wiki]. + +Write the software for the satellite to rebuild the tree from the traversals. + +A pre-order traversal reads the value of the current node before (hence "pre") reading the left subtree in pre-order. +Afterwards the right subtree is read in pre-order. + +An in-order traversal reads the left subtree in-order then the current node and finally the right subtree in-order. +So in order from left to right. + +For example the pre-order traversal of this tree is [a, i, x, f, r]. +The in-order traversal of this tree is [i, a, f, x, r] + +```text + a + / \ +i x + / \ + f r +``` + +Note: the first item in the pre-order traversal is always the root. + +[wiki]: https://en.wikipedia.org/wiki/Tree_traversal diff --git a/exercises/practice/satellite/.meta/Example.fs b/exercises/practice/satellite/.meta/Example.fs new file mode 100644 index 000000000..27cd3a815 --- /dev/null +++ b/exercises/practice/satellite/.meta/Example.fs @@ -0,0 +1,24 @@ +module Satellite + +type Tree = + | Empty + | Node of value: string * left: Tree * right: Tree + +let rec createTree inorder preorder = + match preorder with + | [] -> Empty + | hd :: tail -> + let hdIdx = inorder |> List.findIndex (fun x -> x = hd) + let leftInorder, rightInorder = inorder[0 .. hdIdx - 1], inorder[hdIdx + 1 ..] + let leftPreorder, rightPreorder = tail[0 .. leftInorder.Length - 1], tail[leftInorder.Length ..] + Node(hd, createTree leftInorder leftPreorder, createTree rightInorder rightPreorder) + +let treeFromTraversals preorder inorder = + if List.length preorder <> List.length inorder then + Error "traversals must have the same length" + elif List.sort preorder <> List.sort inorder then + Error "traversals must have the same elements" + elif List.distinct preorder <> preorder then + Error "traversals must contain unique items" + else + Ok(createTree preorder inorder) diff --git a/exercises/practice/satellite/.meta/config.json b/exercises/practice/satellite/.meta/config.json new file mode 100644 index 000000000..660327c48 --- /dev/null +++ b/exercises/practice/satellite/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "erikschierboom" + ], + "files": { + "solution": [ + "Satellite.fs" + ], + "test": [ + "SatelliteTests.fs" + ], + "example": [ + ".meta/Example.fs" + ] + }, + "blurb": "Rebuild binary trees from pre-order and in-order traversals." +} diff --git a/exercises/practice/satellite/.meta/tests.toml b/exercises/practice/satellite/.meta/tests.toml new file mode 100644 index 000000000..b32dc3b13 --- /dev/null +++ b/exercises/practice/satellite/.meta/tests.toml @@ -0,0 +1,28 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[8df3fa26-811a-4165-9286-ff9ac0850d19] +description = "Empty tree" + +[f945ccfc-05e3-47d7-825b-0270559d43ad] +description = "Tree with one item" + +[a0121d5f-37b0-48dd-9c64-cba4c4464135] +description = "Tree with many items" + +[6074041f-4891-4d81-a128-401050c2a3b0] +description = "Reject traversals of different length" + +[27916ce4-45f3-4d8b-8528-496fedc157ca] +description = "Reject inconsistent traversals of same length" + +[d86a3d72-76a9-43b5-9d3a-e64cb1216035] +description = "Reject traversals with repeated items" diff --git a/exercises/practice/satellite/Satellite.fs b/exercises/practice/satellite/Satellite.fs new file mode 100644 index 000000000..ebfbf1ad6 --- /dev/null +++ b/exercises/practice/satellite/Satellite.fs @@ -0,0 +1,8 @@ +module Satellite + +type Tree = + | Empty + | Node of value: string * left: Tree * right: Tree + +let treeFromTraversals preorder inorder = + failwith "Please implement the 'treeFromTraversals' function" diff --git a/exercises/practice/satellite/Satellite.fsproj b/exercises/practice/satellite/Satellite.fsproj new file mode 100644 index 000000000..1c028e39a --- /dev/null +++ b/exercises/practice/satellite/Satellite.fsproj @@ -0,0 +1,22 @@ + + + net8.0 + false + false + true + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + \ No newline at end of file diff --git a/exercises/practice/satellite/SatelliteTests.fs b/exercises/practice/satellite/SatelliteTests.fs new file mode 100644 index 000000000..c0d585b36 --- /dev/null +++ b/exercises/practice/satellite/SatelliteTests.fs @@ -0,0 +1,51 @@ +module SatelliteTests + +open FsUnit.Xunit +open Xunit + +open Satellite + +[] +let ``Empty tree`` () = + let expected: Result = Ok ( + Empty + ) + treeFromTraversals [] [] |> should equal expected + +[] +let ``Tree with one item`` () = + let expected: Result = Ok ( + Node("a", Empty, Empty) + ) + treeFromTraversals ["a"] ["a"] |> should equal expected + +[] +let ``Tree with many items`` () = + let expected: Result = Ok ( + Node( + "a", + Node("i", Empty, Empty), + Node( + "x", + Node("f", Empty, Empty), + Node("r", Empty, Empty) + ) + ) + ) + treeFromTraversals ["i"; "a"; "f"; "x"; "r"] ["a"; "i"; "x"; "f"; "r"] |> should equal expected + +[] +let ``Reject traversals of different length`` () = + let expected: Result = Error "traversals must have the same length" + treeFromTraversals ["b"; "a"; "r"] ["a"; "b"] |> should equal expected + +[] +let ``Reject inconsistent traversals of same length`` () = + let expected: Result = Error "traversals must have the same elements" + treeFromTraversals ["a"; "b"; "c"] ["x"; "y"; "z"] |> should equal expected + +[] +let ``Reject traversals with repeated items`` () = + let expected: Result = Error "traversals must contain unique items" + treeFromTraversals ["b"; "a"; "a"] ["a"; "b"; "a"] |> should equal expected + diff --git a/generators/Generators.fs b/generators/Generators.fs index 8b010f89c..91ba8d83b 100644 --- a/generators/Generators.fs +++ b/generators/Generators.fs @@ -2056,3 +2056,37 @@ type KillerSudokuHelper() = type StateOfTicTacToe() = inherit ExerciseGenerator() + +type Satellite() = + inherit ExerciseGenerator() + + let renderTree (root: JToken) = + let rec render indent (node: JToken) = + let indentation = String(' ', indent * 4) + + if node.HasValues then + let value = node["v"] + let left = node["l"] |> render (indent + 1) + let right = node["r"] |> render (indent + 1) + + if node["l"].HasValues || node["r"].HasValues then + $"{indentation}Node(\n" + + $"{indentation} \"{value}\",\n" + + $"{left},\n" + + $"{right}\n" + + $"{indentation})" + else + $"{indentation}Node(\"{value}\", Empty, Empty)" + else + $"{indentation}Empty" + + render 2 root + + override _.PropertiesWithIdentifier _ = [ "expected" ] + + override _.IdentifierTypeAnnotation(_, _, _) = Some "Result" + + override _.RenderExpected(_, _, value) = + match value.SelectToken "error" with + | null -> $"Ok (\n%s{renderTree value}\n )" + | error -> $"Error \"%s{string error}\"" diff --git a/generators/Properties/launchSettings.json b/generators/Properties/launchSettings.json deleted file mode 100644 index ff0e07d91..000000000 --- a/generators/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Generators": { - "commandName": "Project" - } - } -} \ No newline at end of file