Syncing Up
This commit is contained in:
31
go/tree-building/README.md
Normal file
31
go/tree-building/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Tree Building
|
||||
|
||||
Refactor a tree building algorithm.
|
||||
|
||||
Some web-forums have a tree layout, so posts are presented as a tree. However
|
||||
the posts are typically stored in a database as an unsorted set of records. Thus
|
||||
when presenting the posts to the user the tree structure has to be
|
||||
reconstructed.
|
||||
|
||||
Your job will be to refactor a working but slow and ugly piece of code that
|
||||
implements the tree building logic for highly abstracted records. The records
|
||||
only contain an ID number and a parent ID number. The ID number is always
|
||||
between 0 (inclusive) and the length of the record list (exclusive). No record
|
||||
has a parent ID lower than its own ID and no record, except the root record,
|
||||
has a parent ID that's equal to its own ID.
|
||||
|
||||
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).
|
||||
|
||||
|
||||
|
||||
## Submitting Incomplete Problems
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
||||
|
109
go/tree-building/tree_building.go
Normal file
109
go/tree-building/tree_building.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const testVersion = 3
|
||||
|
||||
type Record struct {
|
||||
ID, Parent int
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
ID int
|
||||
Children []*Node
|
||||
}
|
||||
|
||||
type Mismatch struct{}
|
||||
|
||||
func (m Mismatch) Error() string {
|
||||
return "c"
|
||||
}
|
||||
|
||||
func Build(records []Record) (*Node, error) {
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
root := &Node{}
|
||||
todo := []*Node{root}
|
||||
n := 1
|
||||
for {
|
||||
if len(todo) == 0 {
|
||||
break
|
||||
}
|
||||
newTodo := []*Node(nil)
|
||||
for _, c := range todo {
|
||||
for _, r := range records {
|
||||
if r.Parent == c.ID {
|
||||
if r.ID < c.ID {
|
||||
return nil, errors.New("a")
|
||||
} else if r.ID == c.ID {
|
||||
if r.ID != 0 {
|
||||
return nil, fmt.Errorf("b")
|
||||
}
|
||||
} else {
|
||||
n++
|
||||
switch len(c.Children) {
|
||||
case 0:
|
||||
nn := &Node{ID: r.ID}
|
||||
c.Children = []*Node{nn}
|
||||
newTodo = append(newTodo, nn)
|
||||
case 1:
|
||||
nn := &Node{ID: r.ID}
|
||||
if c.Children[0].ID < r.ID {
|
||||
c.Children = []*Node{c.Children[0], nn}
|
||||
newTodo = append(newTodo, nn)
|
||||
} else {
|
||||
c.Children = []*Node{nn, c.Children[0]}
|
||||
newTodo = append(newTodo, nn)
|
||||
}
|
||||
default:
|
||||
nn := &Node{ID: r.ID}
|
||||
newTodo = append(newTodo, nn)
|
||||
breakpoint:
|
||||
for _ = range []bool{false} {
|
||||
for i, cc := range c.Children {
|
||||
if cc.ID > r.ID {
|
||||
a := make([]*Node, len(c.Children)+1)
|
||||
copy(a, c.Children[:i])
|
||||
copy(a[i+1:], c.Children[i:])
|
||||
copy(a[i:i+1], []*Node{nn})
|
||||
c.Children = a
|
||||
break breakpoint
|
||||
}
|
||||
}
|
||||
c.Children = append(c.Children, nn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
todo = newTodo
|
||||
}
|
||||
if n != len(records) {
|
||||
return nil, Mismatch{}
|
||||
}
|
||||
if err := chk(root, len(records)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func chk(n *Node, m int) (err error) {
|
||||
if n.ID > m {
|
||||
return fmt.Errorf("z")
|
||||
} else if n.ID == m {
|
||||
return fmt.Errorf("y")
|
||||
} else {
|
||||
for i := 0; i < len(n.Children); i++ {
|
||||
err = chk(n.Children[i], m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
305
go/tree-building/tree_test.go
Normal file
305
go/tree-building/tree_test.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Define a function Build(records []Record) (*Node, error)
|
||||
// where Record is a struct containing int fields ID and Parent
|
||||
// and Node is a struct containing int field ID and []*Node field Children.
|
||||
//
|
||||
// Also define a testVersion with a value that matches
|
||||
// the targetTestVersion here.
|
||||
|
||||
const targetTestVersion = 3
|
||||
|
||||
var successTestCases = []struct {
|
||||
name string
|
||||
input []Record
|
||||
expected *Node
|
||||
}{
|
||||
{
|
||||
name: "empty input",
|
||||
input: []Record{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "one node",
|
||||
input: []Record{
|
||||
{ID: 0},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three nodes in order",
|
||||
input: []Record{
|
||||
{ID: 0},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 2, Parent: 0},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
Children: []*Node{
|
||||
{ID: 1},
|
||||
{ID: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three nodes in reverse order",
|
||||
input: []Record{
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
Children: []*Node{
|
||||
{ID: 1},
|
||||
{ID: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "more than two children",
|
||||
input: []Record{
|
||||
{ID: 3, Parent: 0},
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
Children: []*Node{
|
||||
{ID: 1},
|
||||
{ID: 2},
|
||||
{ID: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "binary tree",
|
||||
input: []Record{
|
||||
{ID: 5, Parent: 1},
|
||||
{ID: 3, Parent: 2},
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 4, Parent: 1},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
{ID: 6, Parent: 2},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
Children: []*Node{
|
||||
{
|
||||
ID: 1,
|
||||
Children: []*Node{
|
||||
{ID: 4},
|
||||
{ID: 5},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Children: []*Node{
|
||||
{ID: 3},
|
||||
{ID: 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unbalanced tree",
|
||||
input: []Record{
|
||||
{ID: 5, Parent: 2},
|
||||
{ID: 3, Parent: 2},
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 4, Parent: 1},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
{ID: 6, Parent: 2},
|
||||
},
|
||||
expected: &Node{
|
||||
ID: 0,
|
||||
Children: []*Node{
|
||||
{
|
||||
ID: 1,
|
||||
Children: []*Node{
|
||||
{ID: 4},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Children: []*Node{
|
||||
{ID: 3},
|
||||
{ID: 5},
|
||||
{ID: 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var failureTestCases = []struct {
|
||||
name string
|
||||
input []Record
|
||||
}{
|
||||
{
|
||||
name: "root node has parent",
|
||||
input: []Record{
|
||||
{ID: 0, Parent: 1},
|
||||
{ID: 1, Parent: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no root node",
|
||||
input: []Record{
|
||||
{ID: 1, Parent: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-continuous",
|
||||
input: []Record{
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 4, Parent: 2},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cycle directly",
|
||||
input: []Record{
|
||||
{ID: 5, Parent: 2},
|
||||
{ID: 3, Parent: 2},
|
||||
{ID: 2, Parent: 2},
|
||||
{ID: 4, Parent: 1},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
{ID: 6, Parent: 3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cycle indirectly",
|
||||
input: []Record{
|
||||
{ID: 5, Parent: 2},
|
||||
{ID: 3, Parent: 2},
|
||||
{ID: 2, Parent: 6},
|
||||
{ID: 4, Parent: 1},
|
||||
{ID: 1, Parent: 0},
|
||||
{ID: 0},
|
||||
{ID: 6, Parent: 3},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "higher id parent of lower id",
|
||||
input: []Record{
|
||||
{ID: 0},
|
||||
{ID: 2, Parent: 0},
|
||||
{ID: 1, Parent: 2},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (n Node) String() string {
|
||||
return fmt.Sprintf("%d:%s", n.ID, n.Children)
|
||||
}
|
||||
|
||||
func TestMakeTreeSuccess(t *testing.T) {
|
||||
if testVersion != targetTestVersion {
|
||||
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
|
||||
}
|
||||
for _, tt := range successTestCases {
|
||||
actual, err := Build(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Build for test case %q returned error %q. Error not expected.",
|
||||
tt.name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tt.expected) {
|
||||
t.Fatalf("Build for test case %q returned %s but was expected to return %s.",
|
||||
tt.name, tt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeTreeFailure(t *testing.T) {
|
||||
for _, tt := range failureTestCases {
|
||||
actual, err := Build(tt.input)
|
||||
if err == nil {
|
||||
t.Fatalf("Build for test case %q returned %s but was expected to fail.",
|
||||
tt.name, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func shuffleRecords(records []Record) []Record {
|
||||
rand := rand.New(rand.NewSource(42))
|
||||
newRecords := make([]Record, len(records))
|
||||
for i, idx := range rand.Perm(len(records)) {
|
||||
newRecords[i] = records[idx]
|
||||
}
|
||||
return newRecords
|
||||
}
|
||||
|
||||
// Binary tree
|
||||
func makeTwoTreeRecords() []Record {
|
||||
records := make([]Record, 1<<16)
|
||||
for i := range records {
|
||||
if i == 0 {
|
||||
records[i] = Record{ID: 0}
|
||||
} else {
|
||||
records[i] = Record{ID: i, Parent: i >> 1}
|
||||
}
|
||||
}
|
||||
return shuffleRecords(records)
|
||||
}
|
||||
|
||||
var twoTreeRecords = makeTwoTreeRecords()
|
||||
|
||||
func BenchmarkTwoTree(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Build(twoTreeRecords)
|
||||
}
|
||||
}
|
||||
|
||||
// Each node but the root node and leaf nodes has ten children.
|
||||
func makeTenTreeRecords() []Record {
|
||||
records := make([]Record, 10000)
|
||||
for i := range records {
|
||||
if i == 0 {
|
||||
records[i] = Record{ID: 0}
|
||||
} else {
|
||||
records[i] = Record{ID: i, Parent: i / 10}
|
||||
}
|
||||
}
|
||||
return shuffleRecords(records)
|
||||
}
|
||||
|
||||
var tenTreeRecords = makeTenTreeRecords()
|
||||
|
||||
func BenchmarkTenTree(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Build(tenTreeRecords)
|
||||
}
|
||||
}
|
||||
|
||||
func makeShallowRecords() []Record {
|
||||
records := make([]Record, 10000)
|
||||
for i := range records {
|
||||
records[i] = Record{ID: i, Parent: 0}
|
||||
}
|
||||
return shuffleRecords(records)
|
||||
}
|
||||
|
||||
var shallowRecords = makeShallowRecords()
|
||||
|
||||
func BenchmarkShallowTree(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Build(shallowRecords)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user