package pov import ( "fmt" "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 TestTestVersion(t *testing.T) { if testVersion != targetTestVersion { t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion) } } func TestConstruction(t *testing.T) { fmt.Println("TestConstruction") for _, tc := range testCases { fmt.Println("Starting test:", tc.description) got := tc.graph().ArcList() want := tc.arcStrings tc.testResult(got, want, "incorrect graph construction", t) fmt.Println(" Test Passed:", tc.description) } } 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") } } }