trackler 2.2.1.77 → 2.2.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/problem-specifications/exercises/transpose/canonical-data.json +94 -116
- data/tracks/bash/config/maintainers.json +11 -1
- data/tracks/clojure/config.json +10 -2
- data/tracks/clojure/exercises/reverse-string/README.md +14 -0
- data/tracks/clojure/exercises/reverse-string/project.clj +4 -0
- data/tracks/clojure/exercises/reverse-string/src/example.clj +5 -0
- data/tracks/clojure/exercises/reverse-string/src/reverse_string.clj +5 -0
- data/tracks/clojure/exercises/reverse-string/test/reverse_string_test.clj +18 -0
- data/tracks/clojure/exercises/say/README.md +70 -0
- data/tracks/clojure/exercises/{two-fer → say}/project.clj +3 -3
- data/tracks/clojure/exercises/say/src/example.clj +9 -0
- data/tracks/clojure/exercises/say/src/say.clj +5 -0
- data/tracks/clojure/exercises/say/test/say_test.clj +48 -0
- data/tracks/coffeescript/docs/ABOUT.md +4 -9
- data/tracks/coffeescript/docs/INSTALLATION.md +2 -2
- data/tracks/erlang/exercises/pangram/rebar.config +1 -1
- data/tracks/erlang/exercises/pangram/src/example.erl +2 -3
- data/tracks/erlang/exercises/pangram/test/pangram_tests.erl +12 -13
- data/tracks/fsharp/exercises/grep/Example.fs +16 -20
- data/tracks/fsharp/exercises/grep/GrepTest.fs +154 -233
- data/tracks/fsharp/generators/Common.fs +7 -2
- data/tracks/fsharp/generators/Exercise.fs +51 -16
- data/tracks/fsharp/generators/Formatting.fs +13 -6
- data/tracks/fsharp/generators/Generators.fs +44 -14
- data/tracks/fsharp/generators/Rendering.fs +8 -1
- data/tracks/fsharp/generators/Templates/Generators/_GrepSetup.liquid +37 -0
- data/tracks/fsharp/generators/Templates/_TestClass.liquid +9 -3
- data/tracks/fsharp/generators/Templates/{_TestMethod.liquid → _TestFunction.liquid} +0 -0
- data/tracks/fsharp/generators/Templates/{_TestMethodBody.liquid → _TestFunctionBody.liquid} +0 -0
- data/tracks/fsharp/generators/Templates/_TestMember.liquid +3 -0
- data/tracks/fsharp/generators/Templates/_TestMemberBody.liquid +4 -0
- data/tracks/fsharp/generators/Templates/_TestModule.liquid +17 -0
- data/tracks/go/config.json +24 -0
- data/tracks/go/exercises/acronym/.meta/gen.go +5 -3
- data/tracks/go/exercises/acronym/acronym.go +0 -2
- data/tracks/go/exercises/acronym/cases_test.go +2 -2
- data/tracks/go/exercises/acronym/example.go +2 -3
- data/tracks/go/exercises/all-your-base/.meta/gen.go +9 -7
- data/tracks/go/exercises/all-your-base/cases_test.go +2 -2
- data/tracks/go/exercises/allergies/.meta/gen.go +10 -6
- data/tracks/go/exercises/allergies/cases_test.go +2 -2
- data/tracks/go/exercises/anagram/.meta/gen.go +7 -5
- data/tracks/go/exercises/anagram/cases_test.go +2 -2
- data/tracks/go/exercises/armstrong-numbers/.meta/gen.go +54 -0
- data/tracks/go/exercises/armstrong-numbers/armstrong_test.go +12 -0
- data/tracks/go/exercises/armstrong-numbers/cases_test.go +52 -0
- data/tracks/go/exercises/armstrong-numbers/example.go +24 -0
- data/tracks/go/exercises/binary-search/.meta/gen.go +7 -5
- data/tracks/go/exercises/binary-search/cases_test.go +2 -2
- data/tracks/go/exercises/book-store/.meta/gen.go +5 -3
- data/tracks/go/exercises/book-store/cases_test.go +2 -2
- data/tracks/go/exercises/connect/example.go +4 -5
- data/tracks/go/exercises/dominoes/.meta/gen.go +61 -0
- data/tracks/go/exercises/dominoes/.meta/hints.md +29 -0
- data/tracks/go/exercises/dominoes/README.md +67 -0
- data/tracks/go/exercises/dominoes/cases_test.go +72 -0
- data/tracks/go/exercises/dominoes/dominoes_test.go +106 -0
- data/tracks/go/exercises/dominoes/example.go +146 -0
- data/tracks/go/exercises/ledger/example.go +1 -1
- data/tracks/groovy/.gitignore +3 -0
- data/tracks/groovy/.travis.yml +10 -1
- data/tracks/groovy/bin/prepeare_exercise_builds.groovy +77 -0
- data/tracks/groovy/build.gradle +25 -0
- data/tracks/groovy/exercises/difference-of-squares/Example.groovy +3 -3
- data/tracks/groovy/exercises/gigasecond/GigasecondSpec.groovy +3 -3
- data/tracks/groovy/exercises/linked-list/{DoubleLinkedList.groovy → LinkedList.groovy} +0 -0
- data/tracks/groovy/exercises/linked-list/{DoubleLinkedListSpec.groovy → LinkedListSpec.groovy} +0 -0
- data/tracks/groovy/exercises/linked-list/README.md +1 -1
- data/tracks/groovy/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/tracks/groovy/gradle/wrapper/gradle-wrapper.properties +6 -0
- data/tracks/groovy/gradlew +172 -0
- data/tracks/groovy/gradlew.bat +84 -0
- data/tracks/groovy/settings.gradle +9 -0
- data/tracks/ocaml/exercises/acronym/.merlin +1 -1
- data/tracks/ocaml/exercises/all-your-base/.merlin +1 -1
- data/tracks/ocaml/exercises/anagram/.merlin +1 -1
- data/tracks/ocaml/exercises/atbash-cipher/.merlin +1 -1
- data/tracks/ocaml/exercises/beer-song/.merlin +1 -1
- data/tracks/ocaml/exercises/binary-search/.merlin +1 -1
- data/tracks/ocaml/exercises/bob/.merlin +1 -1
- data/tracks/ocaml/exercises/bowling/.merlin +1 -1
- data/tracks/ocaml/exercises/change/.merlin +1 -1
- data/tracks/ocaml/exercises/difference-of-squares/.merlin +1 -1
- data/tracks/ocaml/exercises/etl/.merlin +1 -1
- data/tracks/ocaml/exercises/forth/.merlin +1 -1
- data/tracks/ocaml/exercises/grade-school/.merlin +1 -1
- data/tracks/ocaml/exercises/hamming/.merlin +1 -1
- data/tracks/ocaml/exercises/hangman/.merlin +1 -1
- data/tracks/ocaml/exercises/hello-world/.merlin +1 -1
- data/tracks/ocaml/exercises/hexadecimal/.merlin +1 -1
- data/tracks/ocaml/exercises/leap/.merlin +1 -1
- data/tracks/ocaml/exercises/list-ops/.merlin +1 -1
- data/tracks/ocaml/exercises/luhn/.merlin +1 -1
- data/tracks/ocaml/exercises/meetup/.merlin +1 -1
- data/tracks/ocaml/exercises/minesweeper/.merlin +1 -1
- data/tracks/ocaml/exercises/nucleotide-count/.merlin +1 -1
- data/tracks/ocaml/exercises/phone-number/.merlin +1 -1
- data/tracks/ocaml/exercises/prime-factors/.merlin +1 -1
- data/tracks/ocaml/exercises/raindrops/.merlin +1 -1
- data/tracks/ocaml/exercises/react/.merlin +1 -1
- data/tracks/ocaml/exercises/rectangles/.merlin +1 -1
- data/tracks/ocaml/exercises/rna-transcription/.merlin +1 -1
- data/tracks/ocaml/exercises/run-length-encoding/.merlin +1 -1
- data/tracks/ocaml/exercises/say/.merlin +1 -1
- data/tracks/ocaml/exercises/space-age/.merlin +1 -1
- data/tracks/ocaml/exercises/triangle/.merlin +1 -1
- data/tracks/ocaml/exercises/word-count/.merlin +1 -1
- data/tracks/ocaml/exercises/zipper/.merlin +1 -1
- data/tracks/typescript/config/maintainers.json +2 -2
- metadata +37 -12
- data/tracks/clojure/exercises/two-fer/README.md +0 -19
- data/tracks/clojure/exercises/two-fer/src/example.clj +0 -5
- data/tracks/clojure/exercises/two-fer/src/two_fer.clj +0 -5
- data/tracks/clojure/exercises/two-fer/test/two_fer_test.clj +0 -12
- data/tracks/erlang/exercises/pangram/include/exercism.hrl +0 -11
|
@@ -31,8 +31,10 @@ type TestGroup struct {
|
|
|
31
31
|
|
|
32
32
|
type OneCase struct {
|
|
33
33
|
Description string
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
Input struct {
|
|
35
|
+
Basket []int
|
|
36
|
+
}
|
|
37
|
+
Expected float64
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
// template applied to above data structure generates the Go test cases
|
|
@@ -47,7 +49,7 @@ var testCases = []struct {
|
|
|
47
49
|
}{{range .J.Groups}}{
|
|
48
50
|
{{range .Cases}} {
|
|
49
51
|
description: "{{.Description}}",
|
|
50
|
-
basket: {{printf "%#v" .Basket}},
|
|
52
|
+
basket: {{printf "%#v" .Input.Basket}},
|
|
51
53
|
expected: {{printf "%.2f" .Expected}},
|
|
52
54
|
},
|
|
53
55
|
{{end}}}{{end}}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
package bookstore
|
|
2
2
|
|
|
3
3
|
// Source: exercism/problem-specifications
|
|
4
|
-
// Commit:
|
|
5
|
-
// Problem Specifications Version: 1.
|
|
4
|
+
// Commit: 78006a2 move "targetgrouping" to "comments"
|
|
5
|
+
// Problem Specifications Version: 1.2.0
|
|
6
6
|
|
|
7
7
|
var testCases = []struct {
|
|
8
8
|
description string
|
|
@@ -43,11 +43,11 @@ func newBoard(lines []string) (board, error) {
|
|
|
43
43
|
if len(lines) < 1 {
|
|
44
44
|
return board{}, errors.New("No lines given")
|
|
45
45
|
}
|
|
46
|
-
height :=
|
|
46
|
+
height := len(lines)
|
|
47
47
|
if len(lines[0]) < 1 {
|
|
48
48
|
return board{}, errors.New("First line is empty string")
|
|
49
49
|
}
|
|
50
|
-
width :=
|
|
50
|
+
width := len(lines[0])
|
|
51
51
|
// This trick for 2D arrays comes from Effective Go
|
|
52
52
|
fields := make([][]int8, height)
|
|
53
53
|
fieldsBacker := make([]int8, height*width)
|
|
@@ -65,12 +65,11 @@ func newBoard(lines []string) (board, error) {
|
|
|
65
65
|
// No need for default, zero value already means no stone
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
return board{
|
|
69
69
|
height: height,
|
|
70
70
|
width: width,
|
|
71
71
|
fields: fields,
|
|
72
|
-
}
|
|
73
|
-
return board, nil
|
|
72
|
+
}, nil
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
// Whether there is a stone of the given color at the given location.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"log"
|
|
6
|
+
"text/template"
|
|
7
|
+
|
|
8
|
+
"../../../gen"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func main() {
|
|
12
|
+
t, err := template.New("").Parse(tmpl)
|
|
13
|
+
if err != nil {
|
|
14
|
+
log.Fatal(err)
|
|
15
|
+
}
|
|
16
|
+
var j js
|
|
17
|
+
if err := gen.Gen("dominoes", &j, t); err != nil {
|
|
18
|
+
log.Fatal(err)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The JSON structure we expect to be able to unmarshal into
|
|
23
|
+
type js struct {
|
|
24
|
+
Exercise string
|
|
25
|
+
Version string
|
|
26
|
+
Comments []string
|
|
27
|
+
Cases []OneCase
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type Dominoe [2]int
|
|
31
|
+
|
|
32
|
+
func (d Dominoe) String() string {
|
|
33
|
+
return fmt.Sprintf("Dominoe{%d, %d}", d[0], d[1])
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// template applied to above data structure generates the Go test cases
|
|
37
|
+
|
|
38
|
+
type OneCase struct {
|
|
39
|
+
Description string
|
|
40
|
+
Dominoes []Dominoe `json:"input"`
|
|
41
|
+
Expected bool
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//func (c OneCase)
|
|
45
|
+
// Template to generate list of test cases.
|
|
46
|
+
var tmpl = `package dominoes
|
|
47
|
+
|
|
48
|
+
{{.Header}}
|
|
49
|
+
|
|
50
|
+
var testCases = []struct {
|
|
51
|
+
description string
|
|
52
|
+
dominoes []Dominoe
|
|
53
|
+
valid bool // true => can chain, false => cannot chain
|
|
54
|
+
}{ {{range .J.Cases}}
|
|
55
|
+
{
|
|
56
|
+
{{printf "%q" .Description}},
|
|
57
|
+
[]Dominoe{ {{range .Dominoes}} {{printf "%v" .}}, {{end}} },
|
|
58
|
+
{{printf "%v" .Expected}},
|
|
59
|
+
}, {{end}}
|
|
60
|
+
}
|
|
61
|
+
`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
## Implementation
|
|
2
|
+
|
|
3
|
+
Define a single Go func, MakeChain, which accepts a slice
|
|
4
|
+
of dominoes and attempts to construct a legal chain of dominoes.
|
|
5
|
+
|
|
6
|
+
MakeChain should have the following signature:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
type Dominoe [2]int
|
|
10
|
+
|
|
11
|
+
func MakeChain(input []Dominoe) (chain []Dominoe, ok bool)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The 'ok' bool result indicates whether the given input dominoe list
|
|
15
|
+
could be arranged in a legal chain. An empty input list is considered legal,
|
|
16
|
+
and a single dominoe whose sides are the same is also considered legal.
|
|
17
|
+
|
|
18
|
+
The 'chain' result is a slice of zero or more dominoes
|
|
19
|
+
arranged in an order which shows the legal chain.
|
|
20
|
+
It is acceptable (and expected) that dominoes in 'input' may need
|
|
21
|
+
to be rotated so that each side matches their adjacent dominoe in the chain.
|
|
22
|
+
Dominoes at the beginning and the end of the chain must also match their outer side.
|
|
23
|
+
|
|
24
|
+
If the given input slice of dominoes cannot be arranged in a legal chain
|
|
25
|
+
MakeChain may return nil for the chain result, but must return false for the ok result.
|
|
26
|
+
|
|
27
|
+
Since there may be more than one legal chain arrangement for a given input list,
|
|
28
|
+
when ok is true, the test program will check the chain for validity only.
|
|
29
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Dominoes
|
|
2
|
+
|
|
3
|
+
Make a chain of dominoes.
|
|
4
|
+
|
|
5
|
+
Compute a way to order a given set of dominoes in such a way that they form a
|
|
6
|
+
correct domino chain (the dots on one half of a stone match the dots on the
|
|
7
|
+
neighbouring half of an adjacent stone) and that dots on the halfs of the stones
|
|
8
|
+
which don't have a neighbour (the first and last stone) match each other.
|
|
9
|
+
|
|
10
|
+
For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something
|
|
11
|
+
like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same.
|
|
12
|
+
|
|
13
|
+
For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3
|
|
14
|
+
|
|
15
|
+
Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used.
|
|
16
|
+
|
|
17
|
+
## Implementation
|
|
18
|
+
|
|
19
|
+
Define a single Go func, MakeChain, which accepts a slice
|
|
20
|
+
of dominoes and attempts to construct a legal chain of dominoes.
|
|
21
|
+
|
|
22
|
+
MakeChain should have the following signature:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
type Dominoe [2]int
|
|
26
|
+
|
|
27
|
+
func MakeChain(input []Dominoe) (chain []Dominoe, ok bool)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The 'ok' bool result indicates whether the given input dominoe list
|
|
31
|
+
could be arranged in a legal chain. An empty input list is considered legal,
|
|
32
|
+
and a single dominoe whose sides are the same is also considered legal.
|
|
33
|
+
|
|
34
|
+
The 'chain' result is a slice of zero or more dominoes
|
|
35
|
+
arranged in an order which shows the legal chain.
|
|
36
|
+
It is acceptable (and expected) that dominoes in 'input' may need
|
|
37
|
+
to be rotated so that each side matches their adjacent dominoe in the chain.
|
|
38
|
+
Dominoes at the beginning and the end of the chain must also match their outer side.
|
|
39
|
+
|
|
40
|
+
If the given input slice of dominoes cannot be arranged in a legal chain
|
|
41
|
+
MakeChain may return nil for the chain result, but must return false for the ok result.
|
|
42
|
+
|
|
43
|
+
Since there may be more than one legal chain arrangement for a given input list,
|
|
44
|
+
when ok is true, the test program will check the chain for validity only.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Running the tests
|
|
49
|
+
|
|
50
|
+
To run the tests run the command `go test` from within the exercise directory.
|
|
51
|
+
|
|
52
|
+
If the test suite contains benchmarks, you can run these with the `-bench`
|
|
53
|
+
flag:
|
|
54
|
+
|
|
55
|
+
go test -bench .
|
|
56
|
+
|
|
57
|
+
Keep in mind that each reviewer will run benchmarks on a different machine, with
|
|
58
|
+
different specs, so the results from these benchmark tests may vary.
|
|
59
|
+
|
|
60
|
+
## Further information
|
|
61
|
+
|
|
62
|
+
For more detailed information about the Go track, including how to get help if
|
|
63
|
+
you're having trouble, please visit the exercism.io [Go language page](http://exercism.io/languages/go/about).
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Submitting Incomplete Solutions
|
|
67
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package dominoes
|
|
2
|
+
|
|
3
|
+
// Source: exercism/problem-specifications
|
|
4
|
+
// Commit: b4ceaf4 Update dominoes canonical-data expected key (#823)
|
|
5
|
+
// Problem Specifications Version: 2.0.0
|
|
6
|
+
|
|
7
|
+
var testCases = []struct {
|
|
8
|
+
description string
|
|
9
|
+
dominoes []Dominoe
|
|
10
|
+
valid bool // true => can chain, false => cannot chain
|
|
11
|
+
}{
|
|
12
|
+
{
|
|
13
|
+
"empty input = empty output",
|
|
14
|
+
[]Dominoe{},
|
|
15
|
+
true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"singleton input = singleton output",
|
|
19
|
+
[]Dominoe{Dominoe{1, 1}},
|
|
20
|
+
true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"singleton that can't be chained",
|
|
24
|
+
[]Dominoe{Dominoe{1, 2}},
|
|
25
|
+
false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"three elements",
|
|
29
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{3, 1}, Dominoe{2, 3}},
|
|
30
|
+
true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"can reverse dominoes",
|
|
34
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{1, 3}, Dominoe{2, 3}},
|
|
35
|
+
true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"can't be chained",
|
|
39
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{4, 1}, Dominoe{2, 3}},
|
|
40
|
+
false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"disconnected - simple",
|
|
44
|
+
[]Dominoe{Dominoe{1, 1}, Dominoe{2, 2}},
|
|
45
|
+
false,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"disconnected - double loop",
|
|
49
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{2, 1}, Dominoe{3, 4}, Dominoe{4, 3}},
|
|
50
|
+
false,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"disconnected - single isolated",
|
|
54
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{2, 3}, Dominoe{3, 1}, Dominoe{4, 4}},
|
|
55
|
+
false,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"need backtrack",
|
|
59
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{2, 3}, Dominoe{3, 1}, Dominoe{2, 4}, Dominoe{2, 4}},
|
|
60
|
+
true,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"separate loops",
|
|
64
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{2, 3}, Dominoe{3, 1}, Dominoe{1, 1}, Dominoe{2, 2}, Dominoe{3, 3}},
|
|
65
|
+
true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"nine elements",
|
|
69
|
+
[]Dominoe{Dominoe{1, 2}, Dominoe{5, 3}, Dominoe{3, 1}, Dominoe{1, 2}, Dominoe{2, 4}, Dominoe{1, 6}, Dominoe{2, 3}, Dominoe{3, 4}, Dominoe{5, 6}},
|
|
70
|
+
true,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package dominoes
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"reflect"
|
|
6
|
+
"sort"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestMakeChain(t *testing.T) {
|
|
11
|
+
for _, test := range testCases {
|
|
12
|
+
c, ok := MakeChain(test.dominoes)
|
|
13
|
+
if ok != test.valid {
|
|
14
|
+
t.Fatalf("FAIL: %s\nMakeChain(%v)\nExpected 'ok' result: %t Actual 'ok': %t",
|
|
15
|
+
test.description, test.dominoes, test.valid, ok)
|
|
16
|
+
}
|
|
17
|
+
if ok {
|
|
18
|
+
// There can be a variety of "valid" chains. Verify the chain is valid.
|
|
19
|
+
if err := verifyChain(test.dominoes, c); err != nil {
|
|
20
|
+
t.Fatalf("FAIL: %s\nVerifying chain failed with error: %v\ninput: %v\nchain: %v",
|
|
21
|
+
test.description, err, test.dominoes, c)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
t.Logf("PASS: %s", test.description)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var (
|
|
29
|
+
errWrongLengthChain = errors.New("wrong length chain")
|
|
30
|
+
errChainIsNotLegalAdj = errors.New("chain is not legal - adjacent mismatch")
|
|
31
|
+
errChainIsNotLegalEnd = errors.New("chain is not legal - ends mismatch")
|
|
32
|
+
errChainSetNotSameAsInputSet = errors.New("chain dominoes not same as input")
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
func verifyChain(input []Dominoe, chain []Dominoe) error {
|
|
36
|
+
if len(input) != len(chain) {
|
|
37
|
+
return errWrongLengthChain
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
switch len(input) {
|
|
41
|
+
case 0:
|
|
42
|
+
return nil
|
|
43
|
+
case 1:
|
|
44
|
+
if input[0] != chain[0] {
|
|
45
|
+
return errChainSetNotSameAsInputSet
|
|
46
|
+
}
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check adjacent pairs.
|
|
51
|
+
for i := 0; i < len(chain)-1; i++ {
|
|
52
|
+
if chain[i][1] != chain[i+1][0] {
|
|
53
|
+
return errChainIsNotLegalAdj
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Check end dominoes.
|
|
57
|
+
if chain[0][0] != chain[len(chain)-1][1] {
|
|
58
|
+
return errChainIsNotLegalEnd
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Make copies of input and chain.
|
|
62
|
+
cinput := copyDominoes(input)
|
|
63
|
+
cchain := copyDominoes(chain)
|
|
64
|
+
|
|
65
|
+
sortDominoes(cinput)
|
|
66
|
+
sortDominoes(cchain)
|
|
67
|
+
|
|
68
|
+
// Compare for equality (same set in input and chain).
|
|
69
|
+
if !reflect.DeepEqual(cinput, cchain) {
|
|
70
|
+
return errChainSetNotSameAsInputSet
|
|
71
|
+
}
|
|
72
|
+
return nil
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func copyDominoes(d []Dominoe) (c []Dominoe) {
|
|
76
|
+
c = make([]Dominoe, len(d))
|
|
77
|
+
// Put each dominoe in "canonical position" [a,b] where a <= b.
|
|
78
|
+
for i := range d {
|
|
79
|
+
c[i] = d[i]
|
|
80
|
+
if c[i][0] > c[i][1] {
|
|
81
|
+
c[i][0], c[i][1] = c[i][1], c[i][0]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return c
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func sortDominoes(d []Dominoe) {
|
|
88
|
+
sort.Slice(d,
|
|
89
|
+
func(i, j int) bool {
|
|
90
|
+
if d[i][0] < d[j][0] {
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
if d[i][0] > d[j][0] {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
return d[i][1] < d[j][1]
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func BenchmarkMakeChain(b *testing.B) {
|
|
101
|
+
for i := 0; i < b.N; i++ {
|
|
102
|
+
for _, test := range testCases {
|
|
103
|
+
MakeChain(test.dominoes)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package dominoes
|
|
2
|
+
|
|
3
|
+
type Dominoe [2]int
|
|
4
|
+
|
|
5
|
+
// MakeChain returns a chain and true if the dominoes in input
|
|
6
|
+
// can be arranged in a legal chain; otherwise it returns nil, false.
|
|
7
|
+
func MakeChain(input []Dominoe) (chain []Dominoe, ok bool) {
|
|
8
|
+
switch len(input) {
|
|
9
|
+
case 0:
|
|
10
|
+
return []Dominoe{}, true
|
|
11
|
+
case 1:
|
|
12
|
+
// A single is legal if the ends match.
|
|
13
|
+
if input[0][0] == input[0][1] {
|
|
14
|
+
return input, true
|
|
15
|
+
}
|
|
16
|
+
return nil, false
|
|
17
|
+
}
|
|
18
|
+
chain, ok = buildDominoeChain(input)
|
|
19
|
+
if !ok {
|
|
20
|
+
return nil, false
|
|
21
|
+
}
|
|
22
|
+
return chain, true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// buildDominoeChain looks for a matching dominoe chain using the 2 or more dominoes in d.
|
|
26
|
+
func buildDominoeChain(d []Dominoe) ([]Dominoe, bool) {
|
|
27
|
+
permuteList := dominoePermutations(d, len(d))
|
|
28
|
+
for _, p := range permuteList {
|
|
29
|
+
chain, ok := arrangeChain(p)
|
|
30
|
+
if ok {
|
|
31
|
+
return chain, true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return nil, false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// arrangeChain uses the dominoes positionally in d[] attempting to make a matching chain;
|
|
38
|
+
// reversing of sides of any dominoe is permitted, but the order of dominoes in d remains
|
|
39
|
+
// the same left to right.
|
|
40
|
+
func arrangeChain(d []Dominoe) (chain []Dominoe, ok bool) {
|
|
41
|
+
// A good chain will match length of d, so preallocate the chain.
|
|
42
|
+
chain = make([]Dominoe, len(d))
|
|
43
|
+
n := 0
|
|
44
|
+
// Put first dominoe into the chain for starting point.
|
|
45
|
+
chain[n] = d[0]
|
|
46
|
+
// Loop through the remaining dominoes in d, attempting to add them into the chain,
|
|
47
|
+
// allowing a reverse of either single one in chain or the new dominoe t at each step.
|
|
48
|
+
for i := 1; i < len(d); i++ {
|
|
49
|
+
t := d[i]
|
|
50
|
+
last := chain[n]
|
|
51
|
+
if n == 0 && last[0] == t[0] {
|
|
52
|
+
// reverse first and only item in chain to match t, and add t to chain.
|
|
53
|
+
chain[n] = reverseDominoe(last)
|
|
54
|
+
chain[n+1] = t
|
|
55
|
+
} else if n == 0 && last[0] == t[1] {
|
|
56
|
+
// reverse only item in chain and t to match, and add reversed t to chain.
|
|
57
|
+
chain[n] = reverseDominoe(last)
|
|
58
|
+
chain[n+1] = reverseDominoe(t)
|
|
59
|
+
} else if last[1] == t[0] {
|
|
60
|
+
// add t as-is into chain.
|
|
61
|
+
chain[n+1] = t
|
|
62
|
+
} else if last[1] == t[1] {
|
|
63
|
+
// reverse t to match last one in chain, and add reversed t to chain.
|
|
64
|
+
chain[n+1] = reverseDominoe(t)
|
|
65
|
+
} else {
|
|
66
|
+
// no match for this chain configuration of d, even with swapping.
|
|
67
|
+
return nil, false
|
|
68
|
+
}
|
|
69
|
+
// chain is now filled in with one more dominoe.
|
|
70
|
+
n++
|
|
71
|
+
}
|
|
72
|
+
// Both ends of the chain must also match.
|
|
73
|
+
if chain[0][0] != chain[len(chain)-1][1] {
|
|
74
|
+
return nil, false
|
|
75
|
+
}
|
|
76
|
+
return chain, true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// reverseDominoe returns given Dominoe x, with its sides reversed/rotated.
|
|
80
|
+
func reverseDominoe(x Dominoe) Dominoe {
|
|
81
|
+
return Dominoe{x[1], x[0]}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// dominoePermutations returns a slice containing the r length permutations of the elements in iterable.
|
|
85
|
+
// The implementation is modeled after the Python itertools.permutations().
|
|
86
|
+
func dominoePermutations(iterable []Dominoe, r int) (perms [][]Dominoe) {
|
|
87
|
+
pool := iterable
|
|
88
|
+
n := len(pool)
|
|
89
|
+
|
|
90
|
+
if r > n {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
indices := make([]int, n)
|
|
95
|
+
for i := range indices {
|
|
96
|
+
indices[i] = i
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cycles := make([]int, r)
|
|
100
|
+
for i := range cycles {
|
|
101
|
+
cycles[i] = n - i
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
result := make([]Dominoe, r)
|
|
105
|
+
for i, el := range indices[:r] {
|
|
106
|
+
result[i] = pool[el]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
p := make([]Dominoe, len(result))
|
|
110
|
+
copy(p, result)
|
|
111
|
+
perms = append(perms, p)
|
|
112
|
+
|
|
113
|
+
for n > 0 {
|
|
114
|
+
i := r - 1
|
|
115
|
+
for ; i >= 0; i-- {
|
|
116
|
+
cycles[i]--
|
|
117
|
+
if cycles[i] == 0 {
|
|
118
|
+
index := indices[i]
|
|
119
|
+
for j := i; j < n-1; j++ {
|
|
120
|
+
indices[j] = indices[j+1]
|
|
121
|
+
}
|
|
122
|
+
indices[n-1] = index
|
|
123
|
+
cycles[i] = n - i
|
|
124
|
+
} else {
|
|
125
|
+
j := cycles[i]
|
|
126
|
+
indices[i], indices[n-j] = indices[n-j], indices[i]
|
|
127
|
+
|
|
128
|
+
for k := i; k < r; k++ {
|
|
129
|
+
result[k] = pool[indices[k]]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
p = make([]Dominoe, len(result))
|
|
133
|
+
copy(p, result)
|
|
134
|
+
perms = append(perms, p)
|
|
135
|
+
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if i < 0 {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
return
|
|
146
|
+
}
|