Syncing Up

This commit is contained in:
2016-09-09 11:24:29 -05:00
parent 148fad68a9
commit 2f99878b09
14 changed files with 1352 additions and 24 deletions

View 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.

View 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
}
}

View 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)
}
}