-
-
Notifications
You must be signed in to change notification settings - Fork 402
[#725] New practice exercise pov
#752
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
Changes from all commits
2c7df41
ac66168
d39a481
8989f5c
0917d03
0937c8c
9187afe
2ea862e
11139f1
998d97b
14251d8
40af16a
7fe1b89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Description | ||
|
||
Reparent a graph on a selected node. | ||
|
||
This exercise is all about re-orientating a graph 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 the same information can be presented from the perspective of any other node | ||
in the graph, by pulling it up to the root and dragging its relationships along | ||
with it. So the same graph 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 graph 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 graph and re-orientating it from the point | ||
of view of one of the nodes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"blurb": "Reparent a graph on a selected node.", | ||
"authors": [ | ||
"jiegillet" | ||
], | ||
"contributors": [ | ||
"neenjaw", | ||
"angelikatyborska" | ||
], | ||
"files": { | ||
"solution": [ | ||
"lib/pov.ex" | ||
], | ||
"test": [ | ||
"test/pov_test.exs" | ||
], | ||
"example": [ | ||
".meta/example.ex" | ||
] | ||
}, | ||
"source": "Adaptation of exercise from 4clojure", | ||
"source_url": "https://www.4clojure.com/" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
defmodule Pov do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this exercise is basically There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Coincidentally, I just reached the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I've solved it now. The approach in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can start with your current implementation like it is now, and then later, if people complain that the exercise is too difficult, we could add hints in the form of a module with a data structure that we suggest using. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, challenging exercises are not as much of an issue for practice exercises |
||
# Structs and types | ||
|
||
@typedoc """ | ||
A tree, which is made of a node with several branches | ||
""" | ||
@type tree :: {any, [tree]} | ||
|
||
defmodule Crumb do | ||
defstruct [:parent, left_siblings: [], right_siblings: []] | ||
@type t :: %Crumb{parent: any, left_siblings: [Pov.tree()], right_siblings: [Pov.tree()]} | ||
end | ||
|
||
defmodule Zipper do | ||
defstruct [:focus, genealogy: []] | ||
@type t :: %Zipper{focus: Pov.tree(), genealogy: [Crumb.t()]} | ||
end | ||
|
||
# Core functions | ||
|
||
@doc """ | ||
Reparent a tree on a selected node. | ||
""" | ||
@spec from_pov(tree :: tree, node :: any) :: tree | {:error, atom} | ||
def from_pov(tree, node) do | ||
case tree |> zip |> search(node) do | ||
{:ok, zipper} -> reparent(zipper) | ||
_ -> {:error, :nonexistent_target} | ||
end | ||
end | ||
|
||
@doc """ | ||
Finds a path between two nodes | ||
""" | ||
@spec path_between(tree :: tree, from :: any, to :: any) :: [any] | {:error, atom} | ||
def path_between(tree, from, to) do | ||
case tree |> zip |> search(from) do | ||
{:ok, zipper} -> | ||
case zipper |> reparent |> zip |> search(to) do | ||
{:ok, zipper_path} -> get_path(zipper_path) | ||
_ -> {:error, :nonexistent_destination} | ||
end | ||
|
||
_ -> | ||
{:error, :nonexistent_source} | ||
end | ||
end | ||
|
||
def search(%Zipper{focus: {node, _children}} = zipper, node), do: {:ok, zipper} | ||
|
||
def search(%Zipper{} = zipper, node) do | ||
case zipper |> down |> search(node) do | ||
{:ok, z} -> {:ok, z} | ||
_ -> zipper |> right |> search(node) | ||
end | ||
end | ||
|
||
def search(nil, _node), do: nil | ||
|
||
def reparent(%Zipper{focus: tree, genealogy: []}), do: tree | ||
|
||
def reparent(%Zipper{ | ||
focus: {node, children}, | ||
genealogy: [ | ||
%Crumb{parent: parent, left_siblings: left, right_siblings: right} | grandparent | ||
] | ||
}) do | ||
{node, [reparent(%Zipper{focus: {parent, left ++ right}, genealogy: grandparent}) | children]} | ||
end | ||
|
||
def get_path(%Zipper{focus: {node, _children}, genealogy: genealogy}) do | ||
parents = Enum.map(genealogy, fn %Crumb{parent: parent} -> parent end) | ||
|
||
Enum.reverse([node | parents]) | ||
end | ||
|
||
# Zipper navigation | ||
# up and left are not actually required for this problem | ||
|
||
def zip(tree), do: %Zipper{focus: tree} | ||
|
||
def down(%Zipper{focus: {value, [child | children]}, genealogy: genealogy}) do | ||
%Zipper{ | ||
focus: child, | ||
genealogy: [%Crumb{parent: value, right_siblings: children} | genealogy] | ||
} | ||
end | ||
|
||
def down(_zipper), do: nil | ||
|
||
def up(%Zipper{ | ||
focus: tree, | ||
genealogy: [ | ||
%Crumb{parent: parent, left_siblings: left, right_siblings: right} | grandparents | ||
] | ||
}) do | ||
%Zipper{focus: {parent, left ++ [tree | right]}, genealogy: grandparents} | ||
end | ||
|
||
def up(_zipper), do: nil | ||
|
||
def left(%Zipper{ | ||
focus: tree, | ||
genealogy: [ | ||
%Crumb{left_siblings: [left | lefties], right_siblings: right} = crumb | grandparents | ||
] | ||
}) do | ||
%Zipper{ | ||
focus: left, | ||
genealogy: [ | ||
%Crumb{crumb | left_siblings: lefties, right_siblings: [tree | right]} | grandparents | ||
] | ||
} | ||
end | ||
|
||
def left(_zipper), do: nil | ||
|
||
def right(%Zipper{ | ||
focus: tree, | ||
genealogy: [ | ||
%Crumb{left_siblings: left, right_siblings: [right | righties]} = crumb | grandparents | ||
] | ||
}) do | ||
%Zipper{ | ||
focus: right, | ||
genealogy: [ | ||
%Crumb{crumb | left_siblings: [tree | left], right_siblings: righties} | grandparents | ||
] | ||
} | ||
end | ||
|
||
def right(_zipper), do: nil | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# This is an auto-generated file. Regular comments will be removed when this | ||
# file is regenerated. Regenerating will not touch any manually added keys, | ||
# so comments can be added in a "comment" key. | ||
|
||
[1b3cd134-49ad-4a7d-8376-7087b7e70792] | ||
description = "Results in the same tree if the input tree is a singleton" | ||
|
||
[0778c745-0636-40de-9edd-25a8f40426f6] | ||
description = "Can reroot a tree with a parent and one sibling" | ||
|
||
[fdfdef0a-4472-4248-8bcf-19cf33f9c06e] | ||
description = "Can reroot a tree with a parent and many siblings" | ||
|
||
[cbcf52db-8667-43d8-a766-5d80cb41b4bb] | ||
description = "Can reroot a tree with new root deeply nested in tree" | ||
|
||
[e27fa4fa-648d-44cd-90af-d64a13d95e06] | ||
description = "Moves children of the new root to same level as former parent" | ||
|
||
[09236c7f-7c83-42cc-87a1-25afa60454a3] | ||
description = "Can reroot a complex tree with cousins" | ||
|
||
[f41d5eeb-8973-448f-a3b0-cc1e019a4193] | ||
description = "Errors if target does not exist in a singleton tree" | ||
|
||
[9dc0a8b3-df02-4267-9a41-693b6aff75e7] | ||
description = "Errors if target does not exist in a large tree" | ||
|
||
[02d1f1d9-428d-4395-b026-2db35ffa8f0a] | ||
description = "Can find path to parent" | ||
|
||
[d0002674-fcfb-4cdc-9efa-bfc54e3c31b5] | ||
description = "Can find path to sibling" | ||
|
||
[c9877cd1-0a69-40d4-b362-725763a5c38f] | ||
description = "Can find path to cousin" | ||
|
||
[9fb17a82-2c14-4261-baa3-2f3f234ffa03] | ||
description = "Can find path not involving root" | ||
|
||
[5124ed49-7845-46ad-bc32-97d5ac7451b2] | ||
description = "Can find path from nodes other than x" | ||
|
||
[f52a183c-25cc-4c87-9fc9-0e7f81a5725c] | ||
description = "Errors if destination does not exist" | ||
|
||
[f4fe18b9-b4a2-4bd5-a694-e179155c2149] | ||
description = "Errors if source does not exist" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
defmodule Pov do | ||
@typedoc """ | ||
A tree, which is made of a node with several branches | ||
""" | ||
@type tree :: {any, [tree]} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great simple choice 👍 |
||
|
||
@doc """ | ||
Reparent a tree on a selected node. | ||
""" | ||
@spec from_pov(tree :: tree, node :: any) :: tree | {:error, atom} | ||
def from_pov(tree, node) do | ||
end | ||
|
||
@doc """ | ||
Finds a path between two nodes | ||
""" | ||
@spec path_between(tree :: tree, from :: any, to :: any) :: [any] | {:error, atom} | ||
def path_between(tree, from, to) do | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
defmodule Pov.MixProject do | ||
use Mix.Project | ||
|
||
def project do | ||
[ | ||
app: :pov, | ||
version: "0.1.0", | ||
# elixir: "~> 1.8", | ||
start_permanent: Mix.env() == :prod, | ||
deps: deps() | ||
] | ||
end | ||
|
||
# Run "mix help compile.app" to learn about applications. | ||
def application do | ||
[ | ||
extra_applications: [:logger] | ||
] | ||
end | ||
|
||
# Run "mix help deps" to learn about dependencies. | ||
defp deps do | ||
[ | ||
# {:dep_from_hexpm, "~> 0.3.0"}, | ||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} | ||
] | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exciting 🤩 looks like this will be our most difficult exercise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not done many of the difficulty 8, but it definitely is one step above Zipper, which is 8.
Forth is a 10 though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think difficult 9 is okay, we don't have a firm definition what each number means anymore, so it's just to give a general feel of the complexity