Skip to content

Initial draft of POV, also added camel->snake custom filter #2076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tmp
/bin/configlet
exercises/*/*/Cargo.lock
clippy.log
.idea
.vscode
.prob-spec
problem-specifications
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,15 @@
"lists",
"unsafe"
]
},
{
"slug": "pov",
"name": "POV",
"uuid": "bf7b7309-3d34-4893-a584-f7742502e012",
"practices": [],
"prerequisites": [],
"difficulty": 10,
"topics": []
}
],
"foregone": [
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)
2 changes: 2 additions & 0 deletions exercises/practice/pov/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
20 changes: 20 additions & 0 deletions exercises/practice/pov/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authors": [
"devcyjung"
],
"files": {
"solution": [
"src/lib.rs",
"Cargo.toml"
],
"test": [
"tests/pov.rs"
],
"example": [
".meta/example.rs"
]
},
"blurb": "Reparent a graph on a selected node.",
"source": "Adaptation of exercise from 4clojure",
"source_url": "https://github.com/oxalorg/4ever-clojure"
}
35 changes: 35 additions & 0 deletions exercises/practice/pov/.meta/example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#[derive(Clone, PartialEq, Eq)]
pub struct Tree {}

impl Tree {
#[must_use]
pub fn new(_label: &str) -> Self {
todo!("Implement a function that creates a new Tree with given label");
}

#[must_use]
pub fn with_children(_label: &str, _children: &[Tree]) -> Self {
todo!("Implement a function that creates a new Tree with given label and Children");
}

#[must_use]
pub fn get_label(&self) -> String {
todo!("Implement getter for label.");
}

#[must_use]
pub fn get_children(&self) -> Vec<&Self> {
todo!("Implement getter for children.");
}

#[must_use]
pub fn pov_from(self, _from: &str) -> Option<Self> {
todo!("Implement a function that reparents Tree with 'from' as root.");
}

#[must_use]
pub fn path_to(self, _from: &str, _to: &str) -> Option<Vec<String>>
{
todo!("Implement a function that returns the list of labels in the shortest path from 'from' to 'to'");
}
}
94 changes: 94 additions & 0 deletions exercises/practice/pov/.meta/test_template.tera
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{%- macro render_tree(tree) -%}
{%- if tree.children -%}
Tree::with_children(
"{{ tree.label }}".to_string(),
vec![
{%- for child in tree.children -%}
{{ self::render_tree(tree=child) }},
{%- endfor -%}
],
)
{%- else -%}
Tree::new("{{ tree.label }}".to_string())
{%- endif -%}
{%- endmacro -%}

{%- macro render_vec(values) -%}
vec![
{%- for value in values -%}
"{{ value }}".to_string(),
{%- endfor -%}
]
{%- endmacro -%}

{% for test_group in cases %}
/// {{ test_group.cases[0].property | camel_to_snake }}() tests
/// {{ test_group.description }}
{%- for comment in test_group.comments %}
/// {{ comment }}
{%- endfor %}
mod {{ test_group.description | make_ident }} {
use pov::*;

{% for test in test_group.cases %}
#[test]
#[ignore]
fn {{ test.description | make_ident }}() {
let input = {{ self::render_tree(tree=test.input.tree) }};
let from = "{{ test.input.from }}".to_string();
{%- if test.property == "fromPov" -%}
let result = input.pov_from(&from);
{%- if not test.expected -%}
let expected: Option<Tree<String>> = None;
{%- else -%}
let expected = Some({{ self::render_tree(tree=test.expected) }});
{%- endif -%}
assert!(crate::test_util::tree_option_eq(result, expected));
{%- elif test.property == "pathTo" -%}
let to = "{{ test.input.to }}".to_string();
let result = input.path_to(&from, &to);
{%- if not test.expected -%}
let expected: Option<Vec<String>> = None;
{%- else -%}
let expected = Some({{ self::render_vec(values=test.expected) }});
{%- endif -%}
assert_eq!(result, expected);
{%- else -%}
Invalid property: {{ test.property }}
{%- endif -%}
}
{% endfor %}
}
{% endfor %}
mod test_util {
use pov::*;

pub fn tree_option_eq(lhs: Option<Tree<String>>, rhs: Option<Tree<String>>) -> bool {
match (lhs, rhs) {
(None, None) => true,
(Some(l_inner), Some(r_inner)) => tree_eq(&l_inner, &r_inner),
_ => false,
}
}

pub fn tree_eq(lhs: &Tree<String>, rhs: &Tree<String>) -> bool {
let (l_label, r_label) = (lhs.get_label(), rhs.get_label());
let (mut l_children, mut r_children) = (
lhs.get_children(),
rhs.get_children(),
);
if l_label == r_label && l_children.len() == r_children.len() {
if l_children.len() == 0 {
return true;
}
let key_fn = |child: &&Tree<String>| child.get_label();
l_children.sort_unstable_by_key(key_fn);
r_children.sort_unstable_by_key(key_fn);
return l_children
.iter()
.zip(r_children.iter())
.all(|(&lc, &rc)| tree_eq(lc, rc));
}
false
}
}
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"
9 changes: 9 additions & 0 deletions exercises/practice/pov/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "pov"
version = "0.1.0"
edition = "2024"

# Not all libraries from crates.io are available in Exercism's test runner.
# The full list of available libraries is here:
# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml
[dependencies]
84 changes: 84 additions & 0 deletions exercises/practice/pov/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::ops::{Deref, DerefMut};

#[derive(Clone, PartialEq)]
pub struct Tree<T: Clone + PartialEq> {
label: T,
children: Vec<Box<Tree<T>>>,
}

impl<T: Clone + PartialEq> Tree<T> {
#[must_use]
pub fn new(label: T) -> Self {
Self { label, children: Default::default() }
}

#[must_use]
pub fn with_children(label: T, children: Vec<Tree<T>>) -> Self {
Self { label, children: children.into_iter().map(Box::new).collect() }
}

#[must_use]
pub fn get_label(&self) -> T {
self.label.clone()
}

#[must_use]
pub fn get_children(&self) -> Vec<&Self> {
self.children.iter().map(Box::deref).collect()
}

#[must_use]
pub fn pov_from(&self, from: &T) -> Option<Self> {
// list of (child, parent, child's index in parent.children)
let mut lookup = vec![(self, None, None)];
let mut stack = vec![self];
while let Some(parent) = stack.pop() {
if &parent.label == from {
return self.reparent(parent, lookup.as_slice()).into();
}
lookup.extend(
parent.children.iter()
.map(Box::deref).enumerate()
.map(|(i, child)| (child, Some(parent), Some(i)))
);
stack.extend(parent.children.iter().map(Box::deref));
}
None
}

// lookup is list of (child, parent, child's index in parent.children)
#[must_use]
fn reparent(&self, parent: &Self, lookup: &[(&Self, Option<&Self>, Option<usize>)]) -> Self {
let mut new_root = parent.clone();
let mut current = parent;
let mut clone_mut = &mut new_root;
let find_parent = |child| lookup.iter().find(|(c, _p, _i)| *c == child);
while let Some(&(_, Some(parent), Some(index))) = find_parent(current) {
let mut parent_clone = parent.clone();
parent_clone.children.swap_remove(index);
clone_mut.children.push(Box::new(parent_clone));
current = parent;
let new_box = clone_mut.children.last_mut().expect("We just inserted node, this is not empty");
clone_mut = new_box.deref_mut();
}
new_root
}

#[must_use]
pub fn path_to(&self, from: &T, to: &T) -> Option<Vec<T>>
{
if from != &self.label {
return self.pov_from(from).and_then(|pov| pov.path_to(from, to));
}
if to == &self.label {
return Some(vec![self.label.clone()]);
}
for child in self.children.iter() {
if let Some(mut path) = child.path_to(&child.label, to) {
path.insert(0, self.label.clone());
return Some(path);
}
}
None
}
}
Loading