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