From 2f99878b095637fd77ecc05d41b3297f1d3e7dab Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Fri, 9 Sep 2016 11:24:29 -0500 Subject: [PATCH] Syncing Up --- go/binary-search-tree/README.md | 72 +++++ go/binary-search-tree/binary_search_tree.go | 50 +++ .../binary_search_tree_test.go | 199 ++++++++++++ go/binary-search/README.md | 53 +++ go/binary-search/binary_search.go | 58 ++++ go/binary-search/binary_search_test.go | 193 +++++++++++ go/hexadecimal/hexadecimal.go | 45 ++- go/meetup/README.md | 36 +++ go/meetup/cases_test.go | 110 +++++++ go/meetup/meetup.go | 76 +++++ go/meetup/meetup_test.go | 39 +++ go/tree-building/README.md | 31 ++ go/tree-building/tree_building.go | 109 +++++++ go/tree-building/tree_test.go | 305 ++++++++++++++++++ 14 files changed, 1352 insertions(+), 24 deletions(-) create mode 100644 go/binary-search-tree/README.md create mode 100644 go/binary-search-tree/binary_search_tree.go create mode 100644 go/binary-search-tree/binary_search_tree_test.go create mode 100644 go/binary-search/README.md create mode 100644 go/binary-search/binary_search.go create mode 100644 go/binary-search/binary_search_test.go create mode 100644 go/meetup/README.md create mode 100644 go/meetup/cases_test.go create mode 100644 go/meetup/meetup.go create mode 100644 go/meetup/meetup_test.go create mode 100644 go/tree-building/README.md create mode 100644 go/tree-building/tree_building.go create mode 100644 go/tree-building/tree_test.go diff --git a/go/binary-search-tree/README.md b/go/binary-search-tree/README.md new file mode 100644 index 0000000..781f203 --- /dev/null +++ b/go/binary-search-tree/README.md @@ -0,0 +1,72 @@ +# Binary Search Tree + +Write a program that inserts numbers and searches in a binary tree. + +When we need to represent sorted data, an array does not make a good +data structure. + +Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes +`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can +improve on this by realizing that we only need to make space for the new +item `[1, nil, 3, 4, 5]`, and then adding the item in the space we +added. But this still requires us to shift many elements down by one. + +Binary Search Trees, however, can operate on sorted data much more +efficiently. + +A binary search tree consists of a series of connected nodes. Each node +contains a piece of data (e.g. the number 3), a variable named `left`, +and a variable named `right`. The `left` and `right` variables point at +`nil`, or other nodes. Since these other nodes in turn have other nodes +beneath them, we say that the left and right variables are pointing at +subtrees. All data in the left subtree is less than or equal to the +current node's data, and all data in the right subtree is greater than +the current node's data. + +For example, if we had a node containing the data 4, and we added the +data 2, our tree would look like this: + + 4 + / + 2 + +If we then added 6, it would look like this: + + 4 + / \ + 2 6 + +If we then added 3, it would look like this + + 4 + / \ + 2 6 + \ + 3 + +And if we then added 1, 5, and 7, it would look like this + + 4 + / \ + / \ + 2 6 + / \ / \ + 1 3 5 7 + +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 + +Josh Cheek [https://twitter.com/josh_cheek](https://twitter.com/josh_cheek) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/binary-search-tree/binary_search_tree.go b/go/binary-search-tree/binary_search_tree.go new file mode 100644 index 0000000..eb88fae --- /dev/null +++ b/go/binary-search-tree/binary_search_tree.go @@ -0,0 +1,50 @@ +package binarysearchtree + +// SearchTreeData is a Binary Search Tree node +type SearchTreeData struct { + data int + left *SearchTreeData + right *SearchTreeData +} + +// Bst creates a node from an int +func Bst(n int) *SearchTreeData { + return &SearchTreeData{data: n} +} + +// Insert finds the correct location for a new node and inserts it there. +func (s *SearchTreeData) Insert(n int) *SearchTreeData { + if s == nil { + return Bst(n) + } + if s.data < n { + s.right = s.right.Insert(n) + } else { + s.left = s.left.Insert(n) + } + return s +} + +// MapString maps a function to every node in the search tree and returns +// the []string result of it. +func (s *SearchTreeData) MapString(f func(int) string) []string { + var ret []string + if s != nil { + ret = append(ret, s.left.MapString(f)...) + ret = append(ret, f(s.data)) + ret = append(ret, s.right.MapString(f)...) + } + return ret +} + +// MapInt maps a function to every node in the search tree and returns +// the []int result of it. +func (s *SearchTreeData) MapInt(f func(int) int) []int { + var ret []int + if s != nil { + ret = append(ret, s.left.MapInt(f)...) + ret = append(ret, f(s.data)) + ret = append(ret, s.right.MapInt(f)...) + } + return ret +} diff --git a/go/binary-search-tree/binary_search_tree_test.go b/go/binary-search-tree/binary_search_tree_test.go new file mode 100644 index 0000000..e419d77 --- /dev/null +++ b/go/binary-search-tree/binary_search_tree_test.go @@ -0,0 +1,199 @@ +// API: +// +// type SearchTreeData struct { +// left *SearchTreeData +// data int +// right *SearchTreeData +// } +// +// func Bst(int) *SearchTreeData +// func (*SearchTreeData) Insert(int) +// func (*SearchTreeData) MapString(func(int) string) []string +// func (*SearchTreeData) MapInt(func(int) int) []int + +package binarysearchtree + +import ( + "reflect" + "strconv" + "testing" +) + +func TestDataIsRetained(t *testing.T) { + actual := Bst(4).data + expected := 4 + if actual != expected { + t.Errorf("Bst(4).data: %d, want %d.", actual, expected) + } +} + +func TestInsertingLess(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(2) + + actual := bst.data + expected := 4 + if actual != expected { + t.Errorf("bst.data: %d, want %d.", actual, expected) + } + + actual = bst.left.data + expected = 2 + if actual != expected { + t.Errorf("bst.left.data: %d, want %d.", actual, expected) + } +} + +func TestInsertingSame(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(4) + + actual := bst.data + expected := 4 + if actual != expected { + t.Errorf("bst.data: %d, want %d.", actual, expected) + } + + actual = bst.left.data + expected = 4 + if actual != expected { + t.Errorf("bst.left.data: %d, want %d.", actual, expected) + } +} + +func TestInsertingMore(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(5) + + actual := bst.data + expected := 4 + if actual != expected { + t.Errorf("bst.data: %d, want %d.", actual, expected) + } + + actual = bst.right.data + expected = 5 + if actual != expected { + t.Errorf("bst.data: %d, want %d.", actual, expected) + } +} + +func TestComplexTree(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(2) + bst.Insert(6) + bst.Insert(1) + bst.Insert(3) + bst.Insert(7) + bst.Insert(5) + + actual := bst.data + expected := 4 + if actual != expected { + t.Errorf("bst.data: %d, want %d.", actual, expected) + } + + actual = bst.left.data + expected = 2 + if actual != expected { + t.Errorf("bst.left.data: %d, want %d.", actual, expected) + } + + actual = bst.left.left.data + expected = 1 + if actual != expected { + t.Errorf("bst.left.left.data: %d, want %d.", actual, expected) + } + + actual = bst.left.right.data + expected = 3 + if actual != expected { + t.Errorf("bst.left.right.data: %d, want %d.", actual, expected) + } + + actual = bst.right.data + expected = 6 + if actual != expected { + t.Errorf("bst.right.data: %d, want %d", actual, expected) + } + + actual = bst.right.left.data + expected = 5 + if actual != expected { + t.Errorf("bst.right.left.data: %d, want %d", actual, expected) + } + + actual = bst.right.right.data + expected = 7 + if actual != expected { + t.Errorf("bst.right.right.data: %d, want %d", actual, expected) + } +} + +func TestMapStringWithOneElement(t *testing.T) { + bst := SearchTreeData{data: 4} + + actual := bst.MapString(strconv.Itoa) + expected := []string{"4"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("bst.MapString(): %q, want %q.", actual, expected) + } +} + +func TestMapStringWithSmallElement(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(2) + + actual := bst.MapString(strconv.Itoa) + expected := []string{"2", "4"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("bst.MapString(): %q, want %q.", actual, expected) + } +} + +func TestMapStringWithLargeElement(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(5) + + actual := bst.MapString(strconv.Itoa) + expected := []string{"4", "5"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("bst.MapString(): %q, want %q.", actual, expected) + } +} + +func TestMapStringWithComplexStructure(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(2) + bst.Insert(1) + bst.Insert(3) + bst.Insert(6) + bst.Insert(7) + bst.Insert(5) + + actual := bst.MapString(strconv.Itoa) + expected := []string{"1", "2", "3", "4", "5", "6", "7"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("bst.MapString(): %q, want %q.", actual, expected) + } +} + +func TestMapIntWithComplexStructure(t *testing.T) { + bst := SearchTreeData{data: 4} + bst.Insert(2) + bst.Insert(1) + bst.Insert(3) + bst.Insert(6) + bst.Insert(7) + bst.Insert(5) + + f := func(i int) int { + return i + } + + actual := bst.MapInt(f) + expected := []int{1, 2, 3, 4, 5, 6, 7} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("bst.MapString(): %v, want %v.", actual, expected) + } +} diff --git a/go/binary-search/README.md b/go/binary-search/README.md new file mode 100644 index 0000000..594797e --- /dev/null +++ b/go/binary-search/README.md @@ -0,0 +1,53 @@ +# Binary Search + +Write a program that implements a binary search algorithm. + +Searching a sorted collection is a common task. A dictionary is a sorted +list of word definitions. Given a word, one can find its definition. A +telephone book is a sorted list of people's names, addresses, and +telephone numbers. Knowing someone's name allows one to quickly find +their telephone number and address. + +If the list to be searched contains more than a few items (a dozen, say) +a binary search will require far fewer comparisons than a linear search, +but it imposes the requirement that the list be sorted. + +In computer science, a binary search or half-interval search algorithm +finds the position of a specified input value (the search "key") within +an array sorted by key value. + +In each step, the algorithm compares the search key value with the key +value of the middle element of the array. + +If the keys match, then a matching element has been found and its index, +or position, is returned. + +Otherwise, if the search key is less than the middle element's key, then +the algorithm repeats its action on the sub-array to the left of the +middle element or, if the search key is greater, on the sub-array to the +right. + +If the remaining array to be searched is empty, then the key cannot be +found in the array and a special "not found" indication is returned. + +A binary search halves the number of items to check with each iteration, +so locating an item (or determining its absence) takes logarithmic time. +A binary search is a dichotomic divide and conquer search algorithm. + +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 + +Wikipedia [http://en.wikipedia.org/wiki/Binary_search_algorithm](http://en.wikipedia.org/wiki/Binary_search_algorithm) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/binary-search/binary_search.go b/go/binary-search/binary_search.go new file mode 100644 index 0000000..99fa7f8 --- /dev/null +++ b/go/binary-search/binary_search.go @@ -0,0 +1,58 @@ +package binarysearch + +import "strconv" + +// SearchInts searches a slice of ints for the first position that 'n' should +// be inserted at and keep 'hay' sorted +func SearchInts(hay []int, n int) int { + if len(hay) == 0 || n < hay[0] { + return 0 + } + if n > hay[len(hay)-1] { + return len(hay) + } + + mid := len(hay) / 2 + if hay[mid] < n { + return mid + 1 + SearchInts(hay[mid+1:], n) + } + + if n < hay[mid] { + return SearchInts(hay[:mid], n) + } + + for mid > 0 && hay[mid-1] == n { + mid-- + } + return mid +} + +// Message does the binary search and returns an english string representation +// of the result +func Message(hay []int, n int) string { + if len(hay) == 0 { + return "slice has no values" + } + v := SearchInts(hay, n) + if v < len(hay) && hay[v] == n { + switch v { + case 0: + return itoa(n) + " found at beginning of slice" + case len(hay) - 1: + return itoa(n) + " found at end of slice" + } + return itoa(n) + " found at index " + itoa(v) + } + switch v { + case 0: + return itoa(n) + " < all values" + case len(hay): + return itoa(n) + " > all " + itoa(len(hay)) + " values" + } + return itoa(n) + " > " + itoa(hay[v-1]) + " at index " + itoa(v-1) + ", < " + itoa(hay[v]) + " at index " + itoa(v) +} + +// itoa is because I'm tired of writing strconv.Itoa +func itoa(v int) string { + return strconv.Itoa(v) +} diff --git a/go/binary-search/binary_search_test.go b/go/binary-search/binary_search_test.go new file mode 100644 index 0000000..e819e98 --- /dev/null +++ b/go/binary-search/binary_search_test.go @@ -0,0 +1,193 @@ +// Go has binary search in the standard library. Let's reimplement +// sort.SearchInts with the same API as documented in the standard library +// at http://golang.org/pkg/sort/#Search. Note that there are some differences +// with the exercise README. +// +// * If there are duplicate values of the key you are searching for, you can't +// just stop at the first one you find; you must find the first occurance in +// the slice. +// +// * There is no special "not found" indication. If the search key is not +// present, SearchInts returns the index of the first value greater than the +// search key. If the key is greater than all values in the slice, SearchInts +// returns the length of the slice. +// +// * You can assume the slice is sorted in increasing order. There is no need +// to check that it is sorted. (That would wreck the performance.) +// +// Try it on your own without peeking at the standard library code. + +package binarysearch + +import ( + "math/rand" + "sort" + "testing" +) + +var testData = []struct { + ref string + slice []int + key int + x int // expected result +}{ + {"6 found at index 3", + []int{1, 3, 4, 6, 8, 9, 11}, + 6, 3}, + {"9 found at index 5", + []int{1, 3, 4, 6, 8, 9, 11}, + 9, 5}, + {"3 found at index 1", + []int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144}, + 3, 1}, + {"55 found at index 7", + []int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144}, + 55, 7}, + {"21 found at index 5", + []int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377}, + 21, 5}, + {"34 found at index 6", + []int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377}, + 34, 6}, + + {"1 found at beginning of slice", + []int{1, 3, 6}, + 1, 0}, + {"6 found at end of slice", + []int{1, 3, 6}, + 6, 2}, + {"2 > 1 at index 0, < 3 at index 1", + []int{1, 3, 6}, + 2, 1}, + {"2 < all values", + []int{11, 13, 16}, + 2, 0}, + {"21 > all 3 values", + []int{11, 13, 16}, + 21, 3}, + {"1 found at beginning of slice", + []int{1, 1, 1, 1, 1, 3, 6}, // duplicates + 1, 0}, + {"3 found at index 1", + []int{1, 3, 3, 3, 3, 3, 6}, + 3, 1}, + {"6 found at index 4", + []int{1, 3, 3, 3, 6, 6, 6}, + 6, 4}, + {"-2 > -3 at index 1, < -1 at index 2", + []int{-6, -3, -1}, // negatives + -2, 2}, + {"0 > -7 at index 4, < 1 at index 5", + []int{-19, -17, -15, -12, -7, 1, 14, 35, 69, 124}, + 0, 5}, + {"slice has no values", + nil, 0, 0}, +} + +func TestSearchInts(t *testing.T) { + for _, test := range testData { + if !sort.IntsAreSorted(test.slice) { + t.Skip("Invalid test data") + } + if x := SearchInts(test.slice, test.key); x != test.x { + t.Fatalf("SearchInts(%v, %d) = %d, want %d", + test.slice, test.key, x, test.x) + } + } +} + +// Did you get it? Did you cut and paste from the standard library? +// Whatever. Now show you can work it. +// +// The test program will supply slices and keys and you will write a function +// that searches and returns one of the following messages: +// +// k found at beginning of slice. +// k found at end of slice. +// k found at index fx. +// k < all values. +// k > all n values. +// k > lv at lx, < gv at gx. +// slice has no values. +// +// In your messages, substitute appropritate values for k, lv, and gv; +// substitute indexes for fx, lx, and gx; substitute a number for n. +// +// In the function Message you will demonstrate a number of different ways +// to test the result of SearchInts. Note that you would probably never need +// all of these different tests in a real program. The point of the exercise +// is just to show that it is possible to identify a number of different +// conditions from the return value. +func TestMessage(t *testing.T) { + for _, test := range testData { + if !sort.IntsAreSorted(test.slice) { + t.Skip("Invalid test data") + } + if res := Message(test.slice, test.key); res != test.ref { + t.Fatalf("Message(%v, %d) =\n%q\nwant:\n%q", + test.slice, test.key, res, test.ref) + } + } +} + +// Benchmarks also test searching larger random slices + +func Benchmark1e2(b *testing.B) { + s := make([]int, 1e2) + for i := range s { + s[i] = rand.Intn(len(s)) + } + sort.Ints(s) + k := rand.Intn(len(s)) + ref := sort.SearchInts(s, k) + res := SearchInts(s, k) + if ref != res { + b.Fatalf( + "Search of %d values gave different answer than sort.SearchInts", + len(s)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + SearchInts(s, k) + } +} + +func Benchmark1e4(b *testing.B) { + s := make([]int, 1e4) + for i := range s { + s[i] = rand.Intn(len(s)) + } + sort.Ints(s) + k := rand.Intn(len(s)) + ref := sort.SearchInts(s, k) + res := SearchInts(s, k) + if ref != res { + b.Fatalf( + "Search of %d values gave different answer than sort.SearchInts", + len(s)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + SearchInts(s, k) + } +} + +func Benchmark1e6(b *testing.B) { + s := make([]int, 1e6) + for i := range s { + s[i] = rand.Intn(len(s)) + } + sort.Ints(s) + k := rand.Intn(len(s)) + ref := sort.SearchInts(s, k) + res := SearchInts(s, k) + if ref != res { + b.Fatalf( + "Search of %d values gave different answer than sort.SearchInts", + len(s)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + SearchInts(s, k) + } +} diff --git a/go/hexadecimal/hexadecimal.go b/go/hexadecimal/hexadecimal.go index 8ebb29f..7330d76 100644 --- a/go/hexadecimal/hexadecimal.go +++ b/go/hexadecimal/hexadecimal.go @@ -2,8 +2,6 @@ package hexadecimal import ( "errors" - "fmt" - "math/big" "strings" ) @@ -14,43 +12,42 @@ func ParseHex(in string) (int64, error) { func HandleErrors(in []string) []string { var ret []string for i := range in { + errStr := "none" _, err := convertNumStringBase(in[i], 16) - ret = append(ret, err.Error()) + if err != nil { + errStr = err.Error() + } + ret = append(ret, errStr) } return ret } // convertNumStringBase takes a numeric string and the base of that string // and returns an int64 of the decimal representation -func convertNumStringBase(in string, oldBase int64) (int64, error) { +func convertNumStringBase(in string, base int64) (int64, error) { if in == "" { return 0, errors.New("syntax") } in = strings.ToLower(in) - base := big.NewInt(oldBase) - var sum big.Int + var a, n int64 for i := range in { - var x *big.Int - if in[i] >= '0' && in[i] <= '9' { - x = big.NewInt(int64(in[i] - '0')) - } else if in[i] >= 'a' && in[i] <= 'z' { - x = big.NewInt(int64(in[i]-'a') + 10) - } - fmt.Println("Comparing Converted Value ", x, "<", oldBase) - if x.Int64() > oldBase { + n *= base + switch { + case in[i] >= '0' && in[i] <= '9': + a = int64(in[i] - '0') + case in[i] >= 'a' && in[i] <= 'f': + a = int64(in[i]-'a') + 10 + default: return 0, errors.New("syntax") } + if a > base { + return 0, errors.New("syntax") + } + n += a - pow := big.NewInt(int64(len(in) - i - 1)) - - var n big.Int - n.Exp(base, pow, nil) - n.Mul(x, &n) - - sum.Add(&sum, &n) - if sum.Int64() < 0 { - return 0, errors.New("Integer Overflow") + if n < 0 { + return 0, errors.New("range") } } - return sum.Int64(), nil + return n, nil } diff --git a/go/meetup/README.md b/go/meetup/README.md new file mode 100644 index 0000000..e7c5ec6 --- /dev/null +++ b/go/meetup/README.md @@ -0,0 +1,36 @@ +# Meetup + +Calculate the date of meetups. + +Typically meetups happen on the same day of the week. + +Examples are + +- the first Monday +- the third Tuesday +- the Wednesteenth +- the last Thursday + +Note that "Monteenth", "Tuesteenth", etc are all made up words. There +was a meetup whose members realised that there are exactly 7 days that +end in '-teenth'. Therefore, one is guaranteed that each day of the week +(Monday, Tuesday, ...) will have exactly one date that is named with '-teenth' +in every month. + +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 + +Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month [https://twitter.com/copiousfreetime](https://twitter.com/copiousfreetime) + +## Submitting Incomplete Problems +It's possible to submit an incomplete solution so you can see how others have completed the exercise. + diff --git a/go/meetup/cases_test.go b/go/meetup/cases_test.go new file mode 100644 index 0000000..6d1b61a --- /dev/null +++ b/go/meetup/cases_test.go @@ -0,0 +1,110 @@ +package meetup + +// Source: exercism/x-common +// Commit: b237b7b Merge pull request #124 from soniakeys/meetup-common-tests + +import "time" + +var testCases = []struct { + year int + month time.Month + week WeekSchedule + weekday time.Weekday + expDay int +}{ + {2013, 5, Teenth, time.Monday, 13}, // monteenth of May 2013 + {2013, 8, Teenth, time.Monday, 19}, // monteenth of August 2013 + {2013, 9, Teenth, time.Monday, 16}, // monteenth of September 2013 + {2013, 3, Teenth, time.Tuesday, 19}, // tuesteenth of March 2013 + {2013, 4, Teenth, time.Tuesday, 16}, // tuesteenth of April 2013 + {2013, 8, Teenth, time.Tuesday, 13}, // tuesteenth of August 2013 + {2013, 1, Teenth, time.Wednesday, 16}, // wednesteenth of January 2013 + {2013, 2, Teenth, time.Wednesday, 13}, // wednesteenth of February 2013 + {2013, 6, Teenth, time.Wednesday, 19}, // wednesteenth of June 2013 + {2013, 5, Teenth, time.Thursday, 16}, // thursteenth of May 2013 + {2013, 6, Teenth, time.Thursday, 13}, // thursteenth of June 2013 + {2013, 9, Teenth, time.Thursday, 19}, // thursteenth of September 2013 + {2013, 4, Teenth, time.Friday, 19}, // friteenth of April 2013 + {2013, 8, Teenth, time.Friday, 16}, // friteenth of August 2013 + {2013, 9, Teenth, time.Friday, 13}, // friteenth of September 2013 + {2013, 2, Teenth, time.Saturday, 16}, // saturteenth of February 2013 + {2013, 4, Teenth, time.Saturday, 13}, // saturteenth of April 2013 + {2013, 10, Teenth, time.Saturday, 19}, // saturteenth of October 2013 + {2013, 5, Teenth, time.Sunday, 19}, // sunteenth of May 2013 + {2013, 6, Teenth, time.Sunday, 16}, // sunteenth of June 2013 + {2013, 10, Teenth, time.Sunday, 13}, // sunteenth of October 2013 + {2013, 3, First, time.Monday, 4}, // first Monday of March 2013 + {2013, 4, First, time.Monday, 1}, // first Monday of April 2013 + {2013, 5, First, time.Tuesday, 7}, // first Tuesday of May 2013 + {2013, 6, First, time.Tuesday, 4}, // first Tuesday of June 2013 + {2013, 7, First, time.Wednesday, 3}, // first Wednesday of July 2013 + {2013, 8, First, time.Wednesday, 7}, // first Wednesday of August 2013 + {2013, 9, First, time.Thursday, 5}, // first Thursday of September 2013 + {2013, 10, First, time.Thursday, 3}, // first Thursday of October 2013 + {2013, 11, First, time.Friday, 1}, // first Friday of November 2013 + {2013, 12, First, time.Friday, 6}, // first Friday of December 2013 + {2013, 1, First, time.Saturday, 5}, // first Saturday of January 2013 + {2013, 2, First, time.Saturday, 2}, // first Saturday of February 2013 + {2013, 3, First, time.Sunday, 3}, // first Sunday of March 2013 + {2013, 4, First, time.Sunday, 7}, // first Sunday of April 2013 + {2013, 3, Second, time.Monday, 11}, // second Monday of March 2013 + {2013, 4, Second, time.Monday, 8}, // second Monday of April 2013 + {2013, 5, Second, time.Tuesday, 14}, // second Tuesday of May 2013 + {2013, 6, Second, time.Tuesday, 11}, // second Tuesday of June 2013 + {2013, 7, Second, time.Wednesday, 10}, // second Wednesday of July 2013 + {2013, 8, Second, time.Wednesday, 14}, // second Wednesday of August 2013 + {2013, 9, Second, time.Thursday, 12}, // second Thursday of September 2013 + {2013, 10, Second, time.Thursday, 10}, // second Thursday of October 2013 + {2013, 11, Second, time.Friday, 8}, // second Friday of November 2013 + {2013, 12, Second, time.Friday, 13}, // second Friday of December 2013 + {2013, 1, Second, time.Saturday, 12}, // second Saturday of January 2013 + {2013, 2, Second, time.Saturday, 9}, // second Saturday of February 2013 + {2013, 3, Second, time.Sunday, 10}, // second Sunday of March 2013 + {2013, 4, Second, time.Sunday, 14}, // second Sunday of April 2013 + {2013, 3, Third, time.Monday, 18}, // third Monday of March 2013 + {2013, 4, Third, time.Monday, 15}, // third Monday of April 2013 + {2013, 5, Third, time.Tuesday, 21}, // third Tuesday of May 2013 + {2013, 6, Third, time.Tuesday, 18}, // third Tuesday of June 2013 + {2013, 7, Third, time.Wednesday, 17}, // third Wednesday of July 2013 + {2013, 8, Third, time.Wednesday, 21}, // third Wednesday of August 2013 + {2013, 9, Third, time.Thursday, 19}, // third Thursday of September 2013 + {2013, 10, Third, time.Thursday, 17}, // third Thursday of October 2013 + {2013, 11, Third, time.Friday, 15}, // third Friday of November 2013 + {2013, 12, Third, time.Friday, 20}, // third Friday of December 2013 + {2013, 1, Third, time.Saturday, 19}, // third Saturday of January 2013 + {2013, 2, Third, time.Saturday, 16}, // third Saturday of February 2013 + {2013, 3, Third, time.Sunday, 17}, // third Sunday of March 2013 + {2013, 4, Third, time.Sunday, 21}, // third Sunday of April 2013 + {2013, 3, Fourth, time.Monday, 25}, // fourth Monday of March 2013 + {2013, 4, Fourth, time.Monday, 22}, // fourth Monday of April 2013 + {2013, 5, Fourth, time.Tuesday, 28}, // fourth Tuesday of May 2013 + {2013, 6, Fourth, time.Tuesday, 25}, // fourth Tuesday of June 2013 + {2013, 7, Fourth, time.Wednesday, 24}, // fourth Wednesday of July 2013 + {2013, 8, Fourth, time.Wednesday, 28}, // fourth Wednesday of August 2013 + {2013, 9, Fourth, time.Thursday, 26}, // fourth Thursday of September 2013 + {2013, 10, Fourth, time.Thursday, 24}, // fourth Thursday of October 2013 + {2013, 11, Fourth, time.Friday, 22}, // fourth Friday of November 2013 + {2013, 12, Fourth, time.Friday, 27}, // fourth Friday of December 2013 + {2013, 1, Fourth, time.Saturday, 26}, // fourth Saturday of January 2013 + {2013, 2, Fourth, time.Saturday, 23}, // fourth Saturday of February 2013 + {2013, 3, Fourth, time.Sunday, 24}, // fourth Sunday of March 2013 + {2013, 4, Fourth, time.Sunday, 28}, // fourth Sunday of April 2013 + {2013, 3, Last, time.Monday, 25}, // last Monday of March 2013 + {2013, 4, Last, time.Monday, 29}, // last Monday of April 2013 + {2013, 5, Last, time.Tuesday, 28}, // last Tuesday of May 2013 + {2013, 6, Last, time.Tuesday, 25}, // last Tuesday of June 2013 + {2013, 7, Last, time.Wednesday, 31}, // last Wednesday of July 2013 + {2013, 8, Last, time.Wednesday, 28}, // last Wednesday of August 2013 + {2013, 9, Last, time.Thursday, 26}, // last Thursday of September 2013 + {2013, 10, Last, time.Thursday, 31}, // last Thursday of October 2013 + {2013, 11, Last, time.Friday, 29}, // last Friday of November 2013 + {2013, 12, Last, time.Friday, 27}, // last Friday of December 2013 + {2013, 1, Last, time.Saturday, 26}, // last Saturday of January 2013 + {2013, 2, Last, time.Saturday, 23}, // last Saturday of February 2013 + {2013, 3, Last, time.Sunday, 31}, // last Sunday of March 2013 + {2013, 4, Last, time.Sunday, 28}, // last Sunday of April 2013 + {2012, 2, Last, time.Wednesday, 29}, // last Wednesday of February 2012 + {2014, 12, Last, time.Wednesday, 31}, // last Wednesday of December 2014 + {2015, 2, Last, time.Sunday, 22}, // last Sunday of February 2015 + {2012, 12, First, time.Friday, 7}, // first Friday of December 2012 +} diff --git a/go/meetup/meetup.go b/go/meetup/meetup.go new file mode 100644 index 0000000..a1d9876 --- /dev/null +++ b/go/meetup/meetup.go @@ -0,0 +1,76 @@ +package meetup + +import "time" + +const testVersion = 3 + +type WeekSchedule int + +const ( + First WeekSchedule = iota + Second + Third + Fourth + Last + Teenth +) + +// Day calculates which day of the month the requested day falls on +func Day(week WeekSchedule, weekday time.Weekday, month time.Month, year int) int { + var t time.Time + switch week { + case First: + t = time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) + if t.Weekday() == weekday { + return t.Day() + } + case Second: + t = time.Date(year, month, 8, 0, 0, 0, 0, time.UTC) + if t.Weekday() == weekday { + return t.Day() + } + case Third: + t = time.Date(year, month, 15, 0, 0, 0, 0, time.UTC) + if t.Weekday() == weekday { + return t.Day() + } + case Fourth: + t = time.Date(year, month, 22, 0, 0, 0, 0, time.UTC) + if t.Weekday() == weekday { + return t.Day() + } + case Last: + t = time.Date(year, month+1, 1, 0, 0, 0, 0, time.UTC) + t = t.AddDate(0, 0, -1) + if t.Weekday() == weekday { + return t.Day() + } + return findPrevDay(weekday, t).Day() + case Teenth: + t = time.Date(year, month, 13, 0, 0, 0, 0, time.UTC) + if t.Weekday() == weekday { + return t.Day() + } + } + return findNextDay(weekday, t).Day() +} + +// findNextDay finds the next 'weekday' after t +func findNextDay(weekday time.Weekday, t time.Time) time.Time { + t = t.AddDate(0, 0, 1) + dow := t.Weekday() + for ; dow != weekday; dow = t.Weekday() { + t = t.AddDate(0, 0, 1) + } + return t +} + +// findPrevDay finds the previous 'weekday' before t +func findPrevDay(weekday time.Weekday, t time.Time) time.Time { + t = t.AddDate(0, 0, -1) + dow := t.Weekday() + for ; dow != weekday; dow = t.Weekday() { + t = t.AddDate(0, 0, -1) + } + return t +} diff --git a/go/meetup/meetup_test.go b/go/meetup/meetup_test.go new file mode 100644 index 0000000..28f3d9f --- /dev/null +++ b/go/meetup/meetup_test.go @@ -0,0 +1,39 @@ +package meetup + +import "testing" + +/* API +package meetup +type Weekschedule +WeekSchedule First +WeekSchedule Second +WeekSchedule Third +WeekSchedule Fourth +WeekSchedule Last +WeekSchedule Teenth +func Day(WeekSchedule, time.Weekday, time.Month, int) int +*/ + +const targetTestVersion = 3 + +var weekName = map[WeekSchedule]string{ + First: "first", + Second: "second", + Third: "third", + Fourth: "fourth", + Teenth: "teenth", + Last: "last", +} + +func TestDay(t *testing.T) { + if testVersion != targetTestVersion { + t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion) + } + for _, test := range testCases { + res := Day(test.week, test.weekday, test.month, test.year) + if res != test.expDay { + t.Fatalf("For %s %s of %s 2013 got date of %d, want %d", + weekName[test.week], test.weekday, test.month, res, test.expDay) + } + } +} diff --git a/go/tree-building/README.md b/go/tree-building/README.md new file mode 100644 index 0000000..11f5980 --- /dev/null +++ b/go/tree-building/README.md @@ -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. + diff --git a/go/tree-building/tree_building.go b/go/tree-building/tree_building.go new file mode 100644 index 0000000..b06c87e --- /dev/null +++ b/go/tree-building/tree_building.go @@ -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 + } +} diff --git a/go/tree-building/tree_test.go b/go/tree-building/tree_test.go new file mode 100644 index 0000000..bc2beea --- /dev/null +++ b/go/tree-building/tree_test.go @@ -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) + } +}