2017-08-15
This commit is contained in:
59
go/pov/README.md
Normal file
59
go/pov/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Pov
|
||||
|
||||
Reparent a graph on a selected node
|
||||
|
||||
# Tree Reparenting
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
+------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:
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
To run the tests simply run the command `go test` in the exercise directory.
|
||||
|
||||
If the test suite contains benchmarks, you can run these with the `-bench`
|
||||
flag:
|
||||
|
||||
go test -bench .
|
||||
|
||||
For more detailed info about the Go track see the [help
|
||||
page](http://exercism.io/languages/go).
|
||||
|
||||
## Source
|
||||
|
||||
Adaptation of exercise from 4clojure [https://www.4clojure.com/](https://www.4clojure.com/)
|
||||
|
||||
## Submitting Incomplete Problems
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
122
go/pov/pov.go
Normal file
122
go/pov/pov.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package pov
|
||||
|
||||
const testVersion = 2
|
||||
|
||||
type Graph struct {
|
||||
leaves []Node
|
||||
}
|
||||
|
||||
func New() *Graph {
|
||||
g := new(Graph)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Graph) GetNode(lbl string) *Node {
|
||||
for i := range g.leaves {
|
||||
if n := g.leaves[i].GetNode(lbl); n != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Graph) AddNode(lbl string) {
|
||||
g.leaves = append(g.leaves, Node{label: lbl})
|
||||
}
|
||||
|
||||
func (g *Graph) addRealNode(n *Node) {
|
||||
g.leaves = append(g.leaves, n)
|
||||
}
|
||||
|
||||
func (g *Graph) AddArc(from, to string) {
|
||||
if n := g.GetNode(to); n != nil {
|
||||
n.AddNode(from)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Graph) ArcList() []string {
|
||||
var ret []string
|
||||
for i := range g.leaves {
|
||||
ret = append(ret, g.leaves[i].ArcList()...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Graph) ChangeRoot(oldRoot, newRoot string) *Graph {
|
||||
// First of all, find the newRoot node
|
||||
ret := New()
|
||||
// The new graph will start with newRoot and have newRoot's leaves
|
||||
if rt := g.GetNode(newRoot); rt == nil {
|
||||
return ret
|
||||
}
|
||||
// It'll have one more leaf, it's parent node
|
||||
ret.AddNode(rt)
|
||||
return g
|
||||
}
|
||||
|
||||
func (g *Graph) getPath(from, to string) []string {
|
||||
var ret []string
|
||||
// Get the 'from' node
|
||||
frNode := g.GetNode(from)
|
||||
if frNode == nil {
|
||||
// Couldn't find the starting node
|
||||
return ret
|
||||
}
|
||||
// Just in case we got the same value for both
|
||||
if from == to {
|
||||
return []string{from}
|
||||
}
|
||||
// Found it
|
||||
return frNode.getPath(to)
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
label string
|
||||
leaves []Node
|
||||
}
|
||||
|
||||
func (n *Node) AddNode(lbl string) {
|
||||
n.leaves = append(n.leaves, Node{label: lbl})
|
||||
}
|
||||
|
||||
func (n *Node) GetNode(lbl string) *Node {
|
||||
if n.label == lbl {
|
||||
return n
|
||||
}
|
||||
for i := range n.leaves {
|
||||
if r := n.leaves[i].GetNode(lbl); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) ArcList() []string {
|
||||
var ret []string
|
||||
for i := range n.leaves {
|
||||
ret = append(ret, n.leaves[i].label+" -> "+n.label)
|
||||
ret = append(ret, n.leaves[i].ArcList()...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (n *Node) getPath(to string) []string {
|
||||
ret := []string{n.label}
|
||||
if n.label == to {
|
||||
return ret
|
||||
}
|
||||
var i int
|
||||
var found bool
|
||||
for i = range n.leaves {
|
||||
if n.leaves[i].GetNode(to) != nil {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// We didn't find a path... :(
|
||||
return ret
|
||||
}
|
||||
// n.leaves[i] should be the right leaf now
|
||||
return append(ret, n.leaves[i].getPath(to)...)
|
||||
}
|
295
go/pov/pov_test.go
Normal file
295
go/pov/pov_test.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package pov
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const targetTestVersion = 2
|
||||
|
||||
// POV / reparent / change root of a tree
|
||||
//
|
||||
// API:
|
||||
// type Graph
|
||||
// func New() *Graph
|
||||
// func (*Graph) AddNode(nodeLabel string)
|
||||
// func (*Graph) AddArc(from, to string)
|
||||
// func (*Graph) ArcList() []string
|
||||
// func (*Graph) ChangeRoot(oldRoot, newRoot string) *Graph
|
||||
//
|
||||
// The type name is Graph because you'll probably be implementing a general
|
||||
// directed graph representation, although the test program will only use
|
||||
// it to create a tree. The term "arc" is used here to mean a directed edge.
|
||||
//
|
||||
// The test program will create a graph with New, then use AddNode to add
|
||||
// leaf nodes. After that it will use AddArc to construct the rest of the tree
|
||||
// from the bottom up. That is, the `to` argument will aways specify a node
|
||||
// that has already been added.
|
||||
//
|
||||
// ArcList is a dump method to let the test program see your graph. It must
|
||||
// return a list of all arcs in the graph. Format each arc as a single string
|
||||
// like "from -> to". The test program can then easily sort the list and
|
||||
// compare it to an expected result. You do not need to bother with sorting
|
||||
// the list yourself.
|
||||
//
|
||||
// All this graph construction and dumping must be working before you start
|
||||
// on the interesting part of the exercise, so it is tested separately as
|
||||
// a first test.
|
||||
//
|
||||
// API function ChangeRoot does the interesting part of the exercise.
|
||||
// OldRoot is passed (as a convenience) and you must return a graph with
|
||||
// newRoot as the root. You can modify the original graph in place and
|
||||
// return it or create a new graph and return that. If you return a new
|
||||
// graph you are free to consume or destroy the original graph. Of course
|
||||
// it's nice to leave it unmodified.
|
||||
|
||||
type arc struct{ fr, to string }
|
||||
|
||||
type testCase struct {
|
||||
description string
|
||||
leaves []string
|
||||
arcPairs []arc
|
||||
root string
|
||||
arcStrings []string
|
||||
reRooted []string
|
||||
}
|
||||
|
||||
var testCases = []testCase{
|
||||
{
|
||||
description: "singleton",
|
||||
leaves: []string{"x"},
|
||||
arcPairs: nil,
|
||||
root: "x",
|
||||
arcStrings: nil,
|
||||
reRooted: nil,
|
||||
},
|
||||
{
|
||||
description: "simple tree",
|
||||
leaves: []string{"sibling", "x"},
|
||||
arcPairs: []arc{
|
||||
{"parent", "sibling"},
|
||||
{"parent", "x"},
|
||||
},
|
||||
root: "parent",
|
||||
arcStrings: []string{
|
||||
"parent -> sibling",
|
||||
"parent -> x",
|
||||
},
|
||||
reRooted: []string{
|
||||
"parent -> sibling",
|
||||
"x -> parent",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "large flat",
|
||||
leaves: []string{"sib-a", "sib-b", "x", "sib-c", "sib-d"},
|
||||
arcPairs: []arc{
|
||||
{"parent", "sib-a"},
|
||||
{"parent", "sib-b"},
|
||||
{"parent", "x"},
|
||||
{"parent", "sib-c"},
|
||||
{"parent", "sib-d"},
|
||||
},
|
||||
root: "parent",
|
||||
arcStrings: []string{
|
||||
"parent -> sib-a",
|
||||
"parent -> sib-b",
|
||||
"parent -> sib-c",
|
||||
"parent -> sib-d",
|
||||
"parent -> x",
|
||||
},
|
||||
reRooted: []string{
|
||||
"parent -> sib-a",
|
||||
"parent -> sib-b",
|
||||
"parent -> sib-c",
|
||||
"parent -> sib-d",
|
||||
"x -> parent",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "deeply nested",
|
||||
leaves: []string{"x"},
|
||||
arcPairs: []arc{
|
||||
{"level-4", "x"},
|
||||
{"level-3", "level-4"},
|
||||
{"level-2", "level-3"},
|
||||
{"level-1", "level-2"},
|
||||
{"level-0", "level-1"},
|
||||
},
|
||||
root: "level-0",
|
||||
arcStrings: []string{
|
||||
"level-0 -> level-1",
|
||||
"level-1 -> level-2",
|
||||
"level-2 -> level-3",
|
||||
"level-3 -> level-4",
|
||||
"level-4 -> x",
|
||||
},
|
||||
reRooted: []string{
|
||||
"level-1 -> level-0",
|
||||
"level-2 -> level-1",
|
||||
"level-3 -> level-2",
|
||||
"level-4 -> level-3",
|
||||
"x -> level-4",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "cousins",
|
||||
leaves: []string{"sib-1", "x", "sib-2", "cousin-1", "cousin-2"},
|
||||
arcPairs: []arc{
|
||||
{"parent", "sib-1"},
|
||||
{"parent", "x"},
|
||||
{"parent", "sib-2"},
|
||||
{"aunt", "cousin-1"},
|
||||
{"aunt", "cousin-2"},
|
||||
{"grand-parent", "parent"},
|
||||
{"grand-parent", "aunt"},
|
||||
},
|
||||
root: "grand-parent",
|
||||
arcStrings: []string{
|
||||
"aunt -> cousin-1",
|
||||
"aunt -> cousin-2",
|
||||
"grand-parent -> aunt",
|
||||
"grand-parent -> parent",
|
||||
"parent -> sib-1",
|
||||
"parent -> sib-2",
|
||||
"parent -> x",
|
||||
},
|
||||
reRooted: []string{
|
||||
"aunt -> cousin-1",
|
||||
"aunt -> cousin-2",
|
||||
"grand-parent -> aunt",
|
||||
"parent -> grand-parent",
|
||||
"parent -> sib-1",
|
||||
"parent -> sib-2",
|
||||
"x -> parent",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "target with children",
|
||||
leaves: []string{"child-1", "child-2", "nephew", "niece",
|
||||
"2nd-cousin-1", "2nd-cousin-2", "2nd-cousin-3", "2nd-cousin-4"},
|
||||
arcPairs: []arc{
|
||||
{"x", "child-1"},
|
||||
{"x", "child-2"},
|
||||
{"sibling", "nephew"},
|
||||
{"sibling", "niece"},
|
||||
{"cousin-1", "2nd-cousin-1"},
|
||||
{"cousin-1", "2nd-cousin-2"},
|
||||
{"cousin-2", "2nd-cousin-3"},
|
||||
{"cousin-2", "2nd-cousin-4"},
|
||||
{"parent", "x"},
|
||||
{"parent", "sibling"},
|
||||
{"aunt", "cousin-1"},
|
||||
{"aunt", "cousin-2"},
|
||||
{"grand-parent", "parent"},
|
||||
{"grand-parent", "aunt"},
|
||||
},
|
||||
root: "grand-parent",
|
||||
arcStrings: []string{
|
||||
"aunt -> cousin-1",
|
||||
"aunt -> cousin-2",
|
||||
"cousin-1 -> 2nd-cousin-1",
|
||||
"cousin-1 -> 2nd-cousin-2",
|
||||
"cousin-2 -> 2nd-cousin-3",
|
||||
"cousin-2 -> 2nd-cousin-4",
|
||||
"grand-parent -> aunt",
|
||||
"grand-parent -> parent",
|
||||
"parent -> sibling",
|
||||
"parent -> x",
|
||||
"sibling -> nephew",
|
||||
"sibling -> niece",
|
||||
"x -> child-1",
|
||||
"x -> child-2",
|
||||
},
|
||||
reRooted: []string{
|
||||
"aunt -> cousin-1",
|
||||
"aunt -> cousin-2",
|
||||
"cousin-1 -> 2nd-cousin-1",
|
||||
"cousin-1 -> 2nd-cousin-2",
|
||||
"cousin-2 -> 2nd-cousin-3",
|
||||
"cousin-2 -> 2nd-cousin-4",
|
||||
"grand-parent -> aunt",
|
||||
"parent -> grand-parent",
|
||||
"parent -> sibling",
|
||||
"sibling -> nephew",
|
||||
"sibling -> niece",
|
||||
"x -> child-1",
|
||||
"x -> child-2",
|
||||
"x -> parent",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (tc testCase) graph() *Graph {
|
||||
g := New()
|
||||
for _, l := range tc.leaves {
|
||||
g.AddNode(l)
|
||||
}
|
||||
for _, a := range tc.arcPairs {
|
||||
g.AddArc(a.fr, a.to)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (tc testCase) testResult(got, want []string, msg string, t *testing.T) {
|
||||
if len(got)+len(want) == 0 {
|
||||
return
|
||||
}
|
||||
gs := append([]string{}, got...)
|
||||
sort.Strings(gs)
|
||||
if reflect.DeepEqual(gs, want) {
|
||||
return
|
||||
}
|
||||
// test has failed
|
||||
t.Log(tc.description, "test case")
|
||||
t.Log(msg)
|
||||
t.Logf("got %d arcs:", len(got))
|
||||
for _, s := range got {
|
||||
t.Log(" ", s)
|
||||
}
|
||||
t.Logf("that result sorted:")
|
||||
for _, s := range gs {
|
||||
t.Log(" ", s)
|
||||
}
|
||||
t.Logf("want %d arcs:", len(want))
|
||||
for _, s := range want {
|
||||
t.Log(" ", s)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
func TestConstruction(t *testing.T) {
|
||||
if testVersion != targetTestVersion {
|
||||
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
got := tc.graph().ArcList()
|
||||
want := tc.arcStrings
|
||||
tc.testResult(got, want, "incorrect graph construction", t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeRoot(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
got := tc.graph().ChangeRoot(tc.root, "x").ArcList()
|
||||
want := tc.reRooted
|
||||
tc.testResult(got, want, "incorrect root change", t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConstructOnlyNoChange(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range testCases {
|
||||
tc.graph()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConstructAndChangeRoot(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range testCases {
|
||||
tc.graph().ChangeRoot(tc.root, "x")
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user