Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,14 @@
"prerequisites": [],
"difficulty": 8
},
{
"slug": "pov",
"name": "POV",
"uuid": "6cbdabf6-4a22-43ec-b2e4-6611ea253237",
"practices": [],
"prerequisites": [],
"difficulty": 8
},
{
"slug": "rest-api",
"name": "REST API",
Expand Down
41 changes: 41 additions & 0 deletions exercises/practice/pov/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -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)
99 changes: 99 additions & 0 deletions exercises/practice/pov/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
Pov :: {}.{
Tree := [Empty, Node({ label : Str, children : Set(Tree) })].{

## Are two trees equal?
is_eq : _ # enable the default is_eq implementation for the Tree type

## Return the tree from the point of view of the node with the given label.
## Return Err(NotFound) if no such node is found.
from_pov : Tree, Str -> Try(Tree, [NotFound, ..])
from_pov = |tree, from| {
root_path = tree |> nodes_to_root(from)?
match root_path.first() {
Err(ListWasEmpty) => {
crash "Unreachable: nodes_to_root cannot return Ok([])"
}
Ok(Empty) => {
crash "Unreachable: target cannot be Empty"
}
Ok(Node(target)) => {
parent = root_path.get(1) ?? Empty
match parent {
Empty => Ok(tree) # the target node is already the root of the tree
Node(parent_node) => {
tree_without_target = tree |> drop(from)
from_parent_pov = tree_without_target |> from_pov(parent_node.label)?
new_children = target.children.insert(from_parent_pov)
Node({ label: from, children: new_children }) |> 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)
path_to : Tree, Str, Str -> Try(List(Str), [NotFound, ..])
path_to = |tree, from, to| {
from_tree = tree |> from_pov(to)?
from_tree
|> nodes_to_root(from)?
.map(
|child| {
match child {
Empty => {
crash "Unreachable: trees must never contain Empty children"
}
Node(node) => node.label
}
},
)
|> Ok
}
}
}

## Return all nodes on the path from the target node up to the root.
## If the node is not found, Err(NotFound) is returned.
nodes_to_root : Pov.Tree, Str -> Try(List(Pov.Tree), [NotFound, ..])
nodes_to_root = |tree, label| {
match tree {
Empty => Err(NotFound)
Node(node) => {
if node.label == label
Ok([Node(node)])
else {
node.children
.to_list()
.fold_until(
Err(NotFound),
|state, child| {
match child |> nodes_to_root(label) {
Ok(subPath) => Break(Ok(subPath.append(Node(node))))
Err(NotFound) => Continue(state)
}
},
)
}
}
}
}

## Drop the node with the given label, if it exists
drop : Pov.Tree, Str -> Pov.Tree
drop = |tree, label| {
match tree {
Empty => Empty
Node(node) => {
if node.label == label {
Empty
} else {
filtered_children =
node.children
.map(|child| drop(child, label))
.drop_if(|child| child == Empty)
Node({ label: node.label, children: filtered_children })
}
}
}
}
19 changes: 19 additions & 0 deletions exercises/practice/pov/.meta/config.json
Original file line number Diff line number Diff line change
@@ -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/"
}
59 changes: 59 additions & 0 deletions exercises/practice/pov/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [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.from_list([
{%- 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 = Tree.({{ roc_tree(case["input"]["tree"]) | indent(8) }})
result = tree.from_pov({{ case["input"]["from"] | to_roc }})
{%- if case["expected"] %}
expected = Ok({{ roc_tree(case["expected"]) | indent(8) }})
result == expected
{%- else %}
result == Err(NotFound)
{%- endif %}
}
{% elif case["property"] == "pathTo" %}
expect {
tree = Tree.({{ roc_tree(case["input"]["tree"]) | indent(8) }})
result = tree.path_to({{ 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 %}

{{ macros.footer() }}
55 changes: 55 additions & 0 deletions exercises/practice/pov/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -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"
21 changes: 21 additions & 0 deletions exercises/practice/pov/Pov.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Pov :: {}.{
Tree := [Empty, Node({ label : Str, children : Set(Tree) })].{

## Are two trees equal?
is_eq : _ # enable the default is_eq implementation for the Tree type

## Return the tree from the point of view of the node with the given label.
## Return Err(NotFound) if no such node is found.
from_pov : Tree, Str -> Try(Tree, [NotFound, ..])
from_pov = |tree, from| {
crash "Please implement the 'from_pov' function"
}

## Return the labels of the nodes between the two given nodes
## If either of these nodes don't exist, return Err(NotFound)
path_to : Tree, Str, Str -> Try(List(Str), [NotFound, ..])
path_to = |tree, from, to| {
crash "Please implement the 'path_to' function"
}
}
}
Loading