Skip to content

Commit 64b1a0d

Browse files
senekordevcyjung
andauthored
Add POV exercise (#2094)
Co-authored-by: devcyjung <jcy0011@yahoo.com>
1 parent d2972ac commit 64b1a0d

12 files changed

Lines changed: 628 additions & 0 deletions

File tree

config.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,15 @@
14041404
"macros_by_example"
14051405
]
14061406
},
1407+
{
1408+
"slug": "pov",
1409+
"name": "POV",
1410+
"uuid": "bf7b7309-3d34-4893-a584-f7742502e012",
1411+
"practices": [],
1412+
"prerequisites": [],
1413+
"difficulty": 10,
1414+
"topics": []
1415+
},
14071416
{
14081417
"slug": "poker",
14091418
"name": "Poker",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Instructions append
2+
3+
## Rust Specific Exercise Notes
4+
5+
You are free to choose the internal representation of your tree.
6+
However, watch out if you store the children in an ordered container.
7+
Two trees must compare as equal independent of the children's order.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Instructions
2+
3+
Reparent a tree on a selected node.
4+
5+
A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes are connected but there are no cycles.
6+
That means, there is exactly one path to get from one node to another for any pair of nodes.
7+
8+
This exercise is all about re-orientating a tree to see things from a different point of view.
9+
For example family trees are usually presented from the ancestor's perspective:
10+
11+
```text
12+
+------0------+
13+
| | |
14+
+-1-+ +-2-+ +-3-+
15+
| | | | | |
16+
4 5 6 7 8 9
17+
```
18+
19+
But there is no inherent direction in a tree.
20+
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.
21+
So the same tree from 6's perspective would look like:
22+
23+
```text
24+
6
25+
|
26+
+-----2-----+
27+
| |
28+
7 +-----0-----+
29+
| |
30+
+-1-+ +-3-+
31+
| | | |
32+
4 5 8 9
33+
```
34+
35+
This lets us more simply describe the paths between two nodes.
36+
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.
37+
38+
This exercise involves taking an input tree and re-orientating it from the point of view of one of the nodes.
39+
40+
[wiki-graph]: https://en.wikipedia.org/wiki/Tree_(graph_theory)
41+
[wiki-tree]: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)

exercises/practice/pov/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
Cargo.lock
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": [
3+
"devcyjung",
4+
"senekor"
5+
],
6+
"files": {
7+
"solution": [
8+
"src/lib.rs",
9+
"Cargo.toml"
10+
],
11+
"test": [
12+
"tests/pov.rs"
13+
],
14+
"example": [
15+
".meta/example.rs"
16+
]
17+
},
18+
"blurb": "Reparent a graph on a selected node.",
19+
"source": "Adaptation of exercise from 4clojure",
20+
"source_url": "https://github.com/oxalorg/4ever-clojure"
21+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::fmt::Debug;
2+
3+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
4+
pub struct Tree<T: Debug + Ord> {
5+
label: T,
6+
children: Vec<Box<Tree<T>>>,
7+
}
8+
9+
impl<T: Debug + Ord> Tree<T> {
10+
pub fn new(label: T) -> Self {
11+
Self {
12+
label,
13+
children: Default::default(),
14+
}
15+
}
16+
17+
pub fn with_child(mut self, child: Self) -> Self {
18+
self.children.insert(
19+
self.children
20+
.binary_search_by(|c| c.label.cmp(&child.label))
21+
.unwrap_err(),
22+
Box::new(child),
23+
);
24+
self
25+
}
26+
27+
pub fn pov_from(&mut self, from: &T) -> bool {
28+
self.pov_from_rec(from).is_some()
29+
}
30+
31+
fn pov_from_rec(&mut self, from: &T) -> Option<Vec<usize>> {
32+
if &self.label == from {
33+
return Some(Vec::new());
34+
}
35+
36+
// Run `pov_from_rec` over all children, until finding the one where it
37+
// worked. That also returns the list of indexes to traverse to find the
38+
// insertion point for the old POV.
39+
let (pos, mut index_list) = self
40+
.children
41+
.iter_mut()
42+
.enumerate()
43+
.find_map(|(i, child)| child.pov_from_rec(from).map(|index_list| (i, index_list)))?;
44+
45+
// swap old and new POV
46+
let mut old_pov = self.children.remove(pos);
47+
std::mem::swap(self, &mut old_pov);
48+
49+
// find parent of old POV
50+
let mut parent_of_old_pov = self;
51+
for i in index_list.iter().rev() {
52+
parent_of_old_pov = &mut parent_of_old_pov.children[*i];
53+
}
54+
55+
// put old POV into its new place
56+
let new_idx = parent_of_old_pov
57+
.children
58+
.binary_search_by(|c| c.label.cmp(&old_pov.label))
59+
.unwrap_err();
60+
parent_of_old_pov.children.insert(new_idx, old_pov);
61+
62+
// Record index of old POV such that other recursive calls can insert
63+
// their old POV as the child of ours.
64+
index_list.push(new_idx);
65+
66+
Some(index_list)
67+
}
68+
69+
pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option<Vec<&'a T>> {
70+
if !self.pov_from(to) {
71+
return None;
72+
}
73+
self.path_from(from)
74+
}
75+
76+
fn path_from<'a>(&'a self, from: &'a T) -> Option<Vec<&'a T>> {
77+
if &self.label == from {
78+
return Some(vec![from]);
79+
}
80+
let mut path = self.children.iter().find_map(|c| c.path_from(from))?;
81+
path.push(&self.label);
82+
Some(path)
83+
}
84+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/// Forbid implementations that rely on Clone.
2+
mod no_clone {
3+
use pov::*;
4+
5+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
6+
struct NotClone;
7+
8+
#[test]
9+
#[ignore]
10+
fn doesnt_rely_on_clone() {
11+
let mut tree = Tree::new(NotClone);
12+
assert!(tree.pov_from(&NotClone));
13+
}
14+
}
15+
16+
/// Equality on trees must be independent of the order of children.
17+
mod equality {
18+
use pov::*;
19+
use pretty_assertions::assert_eq;
20+
21+
#[test]
22+
#[ignore]
23+
fn equality_is_order_independent() {
24+
let a = Tree::new("root").with_child(Tree::new("left")).with_child(Tree::new("right"));
25+
let b = Tree::new("root").with_child(Tree::new("right")).with_child(Tree::new("left"));
26+
assert_eq!(a, b);
27+
}
28+
}
29+
30+
{% macro render_tree(tree) -%}
31+
Tree::new("{{ tree.label }}")
32+
{%- if tree.children -%}{%- for child in tree.children -%}
33+
.with_child({{ self::render_tree(tree=child) }})
34+
{%- endfor -%}{%- endif -%}
35+
{%- endmacro -%}
36+
37+
{%- macro render_vec(values) -%}
38+
vec![
39+
{%- for value in values -%}
40+
&"{{ value }}",
41+
{%- endfor -%}
42+
]
43+
{%- endmacro -%}
44+
45+
{% for test_group in cases %}
46+
/// {{ test_group.description }}
47+
mod {{ test_group.cases[0].property | make_ident }} {
48+
use pov::*;
49+
use pretty_assertions::assert_eq;
50+
51+
{% for test in test_group.cases %}
52+
#[test]
53+
#[ignore]
54+
fn {{ test.description | make_ident }}() {
55+
let mut tree = {{ self::render_tree(tree=test.input.tree) }};
56+
{%- if test.property == "fromPov" -%}
57+
{%- if not test.expected -%}
58+
assert!(!tree.pov_from(&"{{ test.input.from }}"));
59+
{%- else -%}
60+
assert!(tree.pov_from(&"{{ test.input.from }}"));
61+
let expected = {{ self::render_tree(tree=test.expected) }};
62+
assert_eq!(tree, expected);
63+
{%- endif -%}
64+
{%- elif test.property == "pathTo" -%}
65+
let result = tree.path_between(&"{{ test.input.from }}", &"{{ test.input.to }}");
66+
{%- if not test.expected -%}
67+
let expected: Option<Vec<_>> = None;
68+
{%- else -%}
69+
let expected = Some({{ self::render_vec(values=test.expected) }});
70+
{%- endif -%}
71+
assert_eq!(result, expected);
72+
{%- else -%}
73+
Invalid property: {{ test.property }}
74+
{%- endif -%}
75+
}
76+
{% endfor %}
77+
}
78+
{% endfor %}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[1b3cd134-49ad-4a7d-8376-7087b7e70792]
13+
description = "Reroot a tree so that its root is the specified node. -> Results in the same tree if the input tree is a singleton"
14+
15+
[0778c745-0636-40de-9edd-25a8f40426f6]
16+
description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and one sibling"
17+
18+
[fdfdef0a-4472-4248-8bcf-19cf33f9c06e]
19+
description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and many siblings"
20+
21+
[cbcf52db-8667-43d8-a766-5d80cb41b4bb]
22+
description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with new root deeply nested in tree"
23+
24+
[e27fa4fa-648d-44cd-90af-d64a13d95e06]
25+
description = "Reroot a tree so that its root is the specified node. -> Moves children of the new root to same level as former parent"
26+
27+
[09236c7f-7c83-42cc-87a1-25afa60454a3]
28+
description = "Reroot a tree so that its root is the specified node. -> Can reroot a complex tree with cousins"
29+
30+
[f41d5eeb-8973-448f-a3b0-cc1e019a4193]
31+
description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a singleton tree"
32+
33+
[9dc0a8b3-df02-4267-9a41-693b6aff75e7]
34+
description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a large tree"
35+
36+
[02d1f1d9-428d-4395-b026-2db35ffa8f0a]
37+
description = "Given two nodes, find the path between them -> Can find path to parent"
38+
39+
[d0002674-fcfb-4cdc-9efa-bfc54e3c31b5]
40+
description = "Given two nodes, find the path between them -> Can find path to sibling"
41+
42+
[c9877cd1-0a69-40d4-b362-725763a5c38f]
43+
description = "Given two nodes, find the path between them -> Can find path to cousin"
44+
45+
[9fb17a82-2c14-4261-baa3-2f3f234ffa03]
46+
description = "Given two nodes, find the path between them -> Can find path not involving root"
47+
48+
[5124ed49-7845-46ad-bc32-97d5ac7451b2]
49+
description = "Given two nodes, find the path between them -> Can find path from nodes other than x"
50+
51+
[f52a183c-25cc-4c87-9fc9-0e7f81a5725c]
52+
description = "Given two nodes, find the path between them -> Errors if destination does not exist"
53+
54+
[f4fe18b9-b4a2-4bd5-a694-e179155c2149]
55+
description = "Given two nodes, find the path between them -> Errors if source does not exist"

exercises/practice/pov/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "pov"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
# Not all libraries from crates.io are available in Exercism's test runner.
7+
# The full list of available libraries is here:
8+
# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
9+
[dependencies]
10+
11+
[dev-dependencies]
12+
pretty_assertions = "*"

exercises/practice/pov/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use std::fmt::Debug;
2+
3+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
4+
pub struct Tree<T: Debug + Ord> {
5+
remove_this: std::marker::PhantomData<T>,
6+
}
7+
8+
impl<T: Debug + Ord> Tree<T> {
9+
pub fn new(label: T) -> Self {
10+
todo!("Create a new tree with the given {label:?}");
11+
}
12+
13+
/// Builder-method for constructing a tree with children
14+
pub fn with_child(self, child: Self) -> Self {
15+
todo!("Add {child:?} to the tree and return the tree");
16+
}
17+
18+
pub fn pov_from(&mut self, from: &T) -> bool {
19+
todo!("Reparent the tree with the label {from:?} as root");
20+
}
21+
22+
pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option<Vec<&'a T>> {
23+
todo!("Return the shortest path between {from:?} and {to:?}");
24+
}
25+
}

0 commit comments

Comments
 (0)