diff --git a/config.json b/config.json index 970222bf..ff9bac4b 100644 --- a/config.json +++ b/config.json @@ -463,6 +463,14 @@ "prerequisites": [], "difficulty": 8 }, + { + "slug": "pov", + "name": "POV", + "uuid": "6cbdabf6-4a22-43ec-b2e4-6611ea253237", + "practices": [], + "prerequisites": [], + "difficulty": 8 + }, { "slug": "rest-api", "name": "REST API", diff --git a/exercises/practice/pov/.docs/instructions.md b/exercises/practice/pov/.docs/instructions.md new file mode 100644 index 00000000..0fdeed22 --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.md @@ -0,0 +1,41 @@ +# Instructions + +Reparent a tree on a selected node. + +A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes are connected but there are no cycles. +That means, there is exactly one path to get from one node to another for any pair of nodes. + +This exercise is all about re-orientating a tree to see things from a different point of view. +For example family trees are usually presented from the ancestor's perspective: + +```text + +------0------+ + | | | + +-1-+ +-2-+ +-3-+ + | | | | | | + 4 5 6 7 8 9 +``` + +But there is no inherent direction in a tree. +The same information can be presented from the perspective of any other node in the tree, by pulling it up to the root and dragging its relationships along with it. +So the same tree from 6's perspective would look like: + +```text + 6 + | + +-----2-----+ + | | + 7 +-----0-----+ + | | + +-1-+ +-3-+ + | | | | + 4 5 8 9 +``` + +This lets us more simply describe the paths between two nodes. +So for example the path from 6-9 (which in the first tree goes up to the root and then down to a different leaf node) can be seen to follow the path 6-2-0-3-9. + +This exercise involves taking an input tree and re-orientating it from the point of view of one of the nodes. + +[wiki-graph]: https://en.wikipedia.org/wiki/Tree_(graph_theory) +[wiki-tree]: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) diff --git a/exercises/practice/pov/.meta/Example.roc b/exercises/practice/pov/.meta/Example.roc new file mode 100644 index 00000000..e2849cee --- /dev/null +++ b/exercises/practice/pov/.meta/Example.roc @@ -0,0 +1,67 @@ +module [fromPov, pathTo] + +Tree : [Empty, Node { label : Str, children : Set Tree }] + +## Return all nodes on the path from the target node up to the root. +## If the node is not found, Err NotFound is returned. +nodesToRoot : Tree, Str -> Result (List Tree) [NotFound] +nodesToRoot = \tree, label -> + when tree is + Empty -> Err NotFound + Node node -> + if node.label == label then + Ok [Node node] + else + node.children + |> Set.walkUntil (Err NotFound) \state, child -> + when child |> nodesToRoot label is + Ok subPath -> subPath |> List.append (Node node) |> Ok |> Break + Err NotFound -> state |> Continue + +## Drop the node with the given label, if it exists +drop : Tree, Str -> Tree +drop = \tree, label -> + when tree is + Empty -> Empty + Node node -> + if node.label == label then + Empty + else + filteredChildren = + node.children + |> Set.map \child -> drop child label + |> Set.dropIf \child -> child == Empty + Node { label: node.label, children: filteredChildren } + +## Return the tree from the point of view of the node with the given label. +## Return Err NotFound if no such node is found. +fromPov : Tree, Str -> Result Tree [NotFound] +fromPov = \tree, from -> + rootPath : List Tree + rootPath = tree |> nodesToRoot? from + when rootPath |> List.first is + Err ListWasEmpty -> crash "Unreachable: nodesToRoot cannot return Ok []" + Ok Empty -> crash "Unreachable: target cannot be Empty" + Ok (Node target) -> + parent = rootPath |> List.get 1 |> Result.withDefault Empty + when parent is + Empty -> Ok tree # the target node is already the root of the tree + Node parentNode -> + treeWithoutTarget = tree |> drop from + fromParentPov = treeWithoutTarget |> fromPov? parentNode.label + newChildren = target.children |> Set.insert fromParentPov + Node { label: from, children: newChildren } |> Ok + +## Find the list of nodes between the two given nodes and return their labels +## If either of these nodes don't exist, return Err NotFound +pathTo : Tree, Str, Str -> Result (List Str) [NotFound] +pathTo = \tree, from, to -> + fromTree = tree |> fromPov? from + fromTree + |> nodesToRoot? to + |> List.map \child -> + when child is + Empty -> crash "Unreachable: trees must never contain Empty children" + Node node -> node.label + |> Ok + diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json new file mode 100644 index 00000000..a1463bae --- /dev/null +++ b/exercises/practice/pov/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "Pov.roc" + ], + "test": [ + "pov-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Reparent a graph on a selected node.", + "source": "Adaptation of exercise from 4clojure", + "source_url": "https://www.4clojure.com/" +} diff --git a/exercises/practice/pov/.meta/template.j2 b/exercises/practice/pov/.meta/template.j2 new file mode 100644 index 00000000..818cf1ed --- /dev/null +++ b/exercises/practice/pov/.meta/template.j2 @@ -0,0 +1,57 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [fromPov, pathTo] + +Tree : [Empty, Node { label : Str, children : Set Tree }] + +{% macro roc_tree(tree) -%} +{%- set children = tree["children"] or [] -%} +{%- if children == [] -%} +Node { label: {{ tree["label"] | to_roc }}, children: Set.empty {} } +{%- else %} +Node { + label: {{ tree["label"] | to_roc }}, + children: Set.fromList [ +{%- for child in (tree["children"]) %} + {{ roc_tree(child) }}, +{%- endfor %} + ] +} +{%- endif %} +{%- endmacro %} + + +{% for supercase in cases %} +## +## {{ supercase["description"] }} +## + +{% for case in supercase["cases"] -%} +# {{ case["description"] }} +{% if case["property"] == "fromPov" %} +expect + tree = {{ roc_tree(case["input"]["tree"]) | indent(8) }} + result = tree |> {{ case["property"] | to_camel }} {{ case["input"]["from"] | to_roc }} + {%- if case["expected"] %} + expected = {{ roc_tree(case["expected"]) | indent(8) }} |> Ok + result == expected + {%- else %} + result == Err NotFound + {%- endif %} +{% elif case["property"] == "pathTo" %} +expect + tree = {{ roc_tree(case["input"]["tree"]) | indent(8) }} + result = tree |> {{ case["property"] | to_camel }} {{ case["input"]["from"] | to_roc }} {{ case["input"]["to"] | to_roc }} + {%- if case["expected"] %} + expected = Ok {{ case["expected"] | to_roc }} + result == expected + {%- else %} + result == Err NotFound + {%- endif %} +{% else %} +# This test case is not supported yet: perhaps you can try implementing it? +{% endif %} +{% endfor %} +{% endfor %} diff --git a/exercises/practice/pov/.meta/tests.toml b/exercises/practice/pov/.meta/tests.toml new file mode 100644 index 00000000..bfa0bb63 --- /dev/null +++ b/exercises/practice/pov/.meta/tests.toml @@ -0,0 +1,55 @@ +# 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. + +[1b3cd134-49ad-4a7d-8376-7087b7e70792] +description = "Reroot a tree so that its root is the specified node. -> Results in the same tree if the input tree is a singleton" + +[0778c745-0636-40de-9edd-25a8f40426f6] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and one sibling" + +[fdfdef0a-4472-4248-8bcf-19cf33f9c06e] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and many siblings" + +[cbcf52db-8667-43d8-a766-5d80cb41b4bb] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with new root deeply nested in tree" + +[e27fa4fa-648d-44cd-90af-d64a13d95e06] +description = "Reroot a tree so that its root is the specified node. -> Moves children of the new root to same level as former parent" + +[09236c7f-7c83-42cc-87a1-25afa60454a3] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a complex tree with cousins" + +[f41d5eeb-8973-448f-a3b0-cc1e019a4193] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a singleton tree" + +[9dc0a8b3-df02-4267-9a41-693b6aff75e7] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a large tree" + +[02d1f1d9-428d-4395-b026-2db35ffa8f0a] +description = "Given two nodes, find the path between them -> Can find path to parent" + +[d0002674-fcfb-4cdc-9efa-bfc54e3c31b5] +description = "Given two nodes, find the path between them -> Can find path to sibling" + +[c9877cd1-0a69-40d4-b362-725763a5c38f] +description = "Given two nodes, find the path between them -> Can find path to cousin" + +[9fb17a82-2c14-4261-baa3-2f3f234ffa03] +description = "Given two nodes, find the path between them -> Can find path not involving root" + +[5124ed49-7845-46ad-bc32-97d5ac7451b2] +description = "Given two nodes, find the path between them -> Can find path from nodes other than x" + +[f52a183c-25cc-4c87-9fc9-0e7f81a5725c] +description = "Given two nodes, find the path between them -> Errors if destination does not exist" + +[f4fe18b9-b4a2-4bd5-a694-e179155c2149] +description = "Given two nodes, find the path between them -> Errors if source does not exist" diff --git a/exercises/practice/pov/Pov.roc b/exercises/practice/pov/Pov.roc new file mode 100644 index 00000000..531a7d48 --- /dev/null +++ b/exercises/practice/pov/Pov.roc @@ -0,0 +1,20 @@ +module [fromPov, pathTo, isEquivalentTo] + +Tree : [Empty, Node { label : Str, children : List Tree }] + +## Are tree1 and tree2 identical, ignoring the order of the children? +isEquivalentTo : Tree, Tree -> Bool +isEquivalentTo = \tree1, tree2 -> + crash "Please implement the 'isEquivalentTo' function" + +## Return the tree from the point of view of the node with the given label. +## Return Err NotFound if no such node is found. +fromPov : Tree, Str -> Result Tree [NotFound] +fromPov = \tree, from -> + crash "Please implement the 'fromPov' function" + +## Return the labels of the nodes between the two given nodes +## If either of these nodes don't exist, return Err NotFound +pathTo : Tree, Str, Str -> Result (List Str) [NotFound] +pathTo = \tree, from, to -> + crash "Please implement the 'pathTo' function" diff --git a/exercises/practice/pov/pov-test.roc b/exercises/practice/pov/pov-test.roc new file mode 100644 index 00000000..c9d88421 --- /dev/null +++ b/exercises/practice/pov/pov-test.roc @@ -0,0 +1,403 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/pov/canonical-data.json +# File last updated on 2024-09-20 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import Pov exposing [fromPov, pathTo] + +Tree : [Empty, Node { label : Str, children : Set Tree }] + +## +## Reroot a tree so that its root is the specified node. +## + +# Results in the same tree if the input tree is a singleton + +expect + tree = Node { label: "x", children: Set.empty {} } + result = tree |> fromPov "x" + expected = Node { label: "x", children: Set.empty {} } |> Ok + result == expected + +# Can reroot a tree with a parent and one sibling + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { label: "x", children: Set.empty {} }, + Node { label: "sibling", children: Set.empty {} }, + ], + } + result = tree |> fromPov "x" + expected = + Node { + label: "x", + children: Set.fromList [ + Node { + label: "parent", + children: Set.fromList [ + Node { label: "sibling", children: Set.empty {} }, + ], + }, + ], + } + |> Ok + result == expected + +# Can reroot a tree with a parent and many siblings + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { label: "a", children: Set.empty {} }, + Node { label: "x", children: Set.empty {} }, + Node { label: "b", children: Set.empty {} }, + Node { label: "c", children: Set.empty {} }, + ], + } + result = tree |> fromPov "x" + expected = + Node { + label: "x", + children: Set.fromList [ + Node { + label: "parent", + children: Set.fromList [ + Node { label: "a", children: Set.empty {} }, + Node { label: "b", children: Set.empty {} }, + Node { label: "c", children: Set.empty {} }, + ], + }, + ], + } + |> Ok + result == expected + +# Can reroot a tree with new root deeply nested in tree + +expect + tree = + Node { + label: "level-0", + children: Set.fromList [ + Node { + label: "level-1", + children: Set.fromList [ + Node { + label: "level-2", + children: Set.fromList [ + Node { + label: "level-3", + children: Set.fromList [ + Node { label: "x", children: Set.empty {} }, + ], + }, + ], + }, + ], + }, + ], + } + result = tree |> fromPov "x" + expected = + Node { + label: "x", + children: Set.fromList [ + Node { + label: "level-3", + children: Set.fromList [ + Node { + label: "level-2", + children: Set.fromList [ + Node { + label: "level-1", + children: Set.fromList [ + Node { label: "level-0", children: Set.empty {} }, + ], + }, + ], + }, + ], + }, + ], + } + |> Ok + result == expected + +# Moves children of the new root to same level as former parent + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + ], + } + result = tree |> fromPov "x" + expected = + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + Node { label: "parent", children: Set.empty {} }, + ], + } + |> Ok + result == expected + +# Can reroot a complex tree with cousins + +expect + tree = + Node { + label: "grandparent", + children: Set.fromList [ + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + }, + Node { + label: "uncle", + children: Set.fromList [ + Node { label: "cousin-0", children: Set.empty {} }, + Node { label: "cousin-1", children: Set.empty {} }, + ], + }, + ], + } + result = tree |> fromPov "x" + expected = + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-1", children: Set.empty {} }, + Node { label: "kid-0", children: Set.empty {} }, + Node { + label: "parent", + children: Set.fromList [ + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + Node { + label: "grandparent", + children: Set.fromList [ + Node { + label: "uncle", + children: Set.fromList [ + Node { label: "cousin-0", children: Set.empty {} }, + Node { label: "cousin-1", children: Set.empty {} }, + ], + }, + ], + }, + ], + }, + ], + } + |> Ok + result == expected + +# Errors if target does not exist in a singleton tree + +expect + tree = Node { label: "x", children: Set.empty {} } + result = tree |> fromPov "nonexistent" + result == Err NotFound + +# Errors if target does not exist in a large tree + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + } + result = tree |> fromPov "nonexistent" + result == Err NotFound + +## +## Given two nodes, find the path between them +## + +# Can find path to parent + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { label: "x", children: Set.empty {} }, + Node { label: "sibling", children: Set.empty {} }, + ], + } + result = tree |> pathTo "x" "parent" + expected = Ok ["x", "parent"] + result == expected + +# Can find path to sibling + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { label: "a", children: Set.empty {} }, + Node { label: "x", children: Set.empty {} }, + Node { label: "b", children: Set.empty {} }, + Node { label: "c", children: Set.empty {} }, + ], + } + result = tree |> pathTo "x" "b" + expected = Ok ["x", "parent", "b"] + result == expected + +# Can find path to cousin + +expect + tree = + Node { + label: "grandparent", + children: Set.fromList [ + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + }, + Node { + label: "uncle", + children: Set.fromList [ + Node { label: "cousin-0", children: Set.empty {} }, + Node { label: "cousin-1", children: Set.empty {} }, + ], + }, + ], + } + result = tree |> pathTo "x" "cousin-1" + expected = Ok ["x", "parent", "grandparent", "uncle", "cousin-1"] + result == expected + +# Can find path not involving root + +expect + tree = + Node { + label: "grandparent", + children: Set.fromList [ + Node { + label: "parent", + children: Set.fromList [ + Node { label: "x", children: Set.empty {} }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + }, + ], + } + result = tree |> pathTo "x" "sibling-1" + expected = Ok ["x", "parent", "sibling-1"] + result == expected + +# Can find path from nodes other than x + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { label: "a", children: Set.empty {} }, + Node { label: "x", children: Set.empty {} }, + Node { label: "b", children: Set.empty {} }, + Node { label: "c", children: Set.empty {} }, + ], + } + result = tree |> pathTo "a" "c" + expected = Ok ["a", "parent", "c"] + result == expected + +# Errors if destination does not exist + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + } + result = tree |> pathTo "x" "nonexistent" + result == Err NotFound + +# Errors if source does not exist + +expect + tree = + Node { + label: "parent", + children: Set.fromList [ + Node { + label: "x", + children: Set.fromList [ + Node { label: "kid-0", children: Set.empty {} }, + Node { label: "kid-1", children: Set.empty {} }, + ], + }, + Node { label: "sibling-0", children: Set.empty {} }, + Node { label: "sibling-1", children: Set.empty {} }, + ], + } + result = tree |> pathTo "nonexistent" "x" + result == Err NotFound +