trackler 2.2.1.86 → 2.2.1.87
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/reverse-string/metadata.yml +0 -1
- data/tracks/bash/CONTRIBUTING.md +129 -28
- data/tracks/bash/config/exercise_readme.go.tmpl +10 -4
- data/tracks/bash/exercises/acronym/README.md +8 -11
- data/tracks/bash/exercises/acronym/{acronym_tests.sh → acronym_test.sh} +0 -0
- data/tracks/bash/exercises/anagram/README.md +6 -1
- data/tracks/bash/exercises/anagram/anagram_test.sh +1 -1
- data/tracks/bash/exercises/armstrong-numbers/README.md +10 -2
- data/tracks/bash/exercises/atbash-cipher/README.md +6 -6
- data/tracks/bash/exercises/bob/README.md +8 -1
- data/tracks/bash/exercises/collatz-conjecture/README.md +11 -2
- data/tracks/bash/exercises/collatz-conjecture/{collatz_test.sh → collatz_conjecture_test.sh} +6 -6
- data/tracks/bash/exercises/difference-of-squares/README.md +6 -1
- data/tracks/bash/exercises/error-handling/README.md +13 -5
- data/tracks/bash/exercises/error-handling/error_handling_test.sh +18 -22
- data/tracks/bash/exercises/error-handling/example.sh +7 -6
- data/tracks/bash/exercises/gigasecond/.meta/hints.md +0 -5
- data/tracks/bash/exercises/gigasecond/README.md +36 -1
- data/tracks/bash/exercises/grains/README.md +10 -2
- data/tracks/bash/exercises/hamming/README.md +6 -1
- data/tracks/bash/exercises/hello-world/README.md +7 -2
- data/tracks/bash/exercises/leap/README.md +7 -2
- data/tracks/bash/exercises/luhn/README.md +7 -2
- data/tracks/bash/exercises/nucleotide-count/README.md +13 -22
- data/tracks/bash/exercises/pangram/README.md +7 -2
- data/tracks/bash/exercises/phone-number/README.md +10 -4
- data/tracks/bash/exercises/raindrops/README.md +6 -1
- data/tracks/bash/exercises/reverse-string/README.md +11 -2
- data/tracks/bash/exercises/rna-transcription/README.md +7 -2
- data/tracks/bash/exercises/roman-numerals/README.md +11 -2
- data/tracks/bash/exercises/triangle/README.md +5 -4
- data/tracks/bash/exercises/two-fer/README.md +7 -9
- data/tracks/bash/exercises/word-count/README.md +6 -2
- data/tracks/bash/exercises/word-count/example.sh +22 -13
- data/tracks/bash/scripts/canonical_data_check.sh +112 -0
- data/tracks/c/exercises/acronym/src/{example.h → acronym.h} +0 -0
- data/tracks/c/exercises/gigasecond/.meta/hints.md +124 -0
- data/tracks/c/exercises/gigasecond/README.md +126 -0
- data/tracks/c/exercises/isogram/src/{example.h → isogram.h} +0 -0
- data/tracks/c/exercises/meetup/.meta/hints.md +124 -0
- data/tracks/c/exercises/meetup/README.md +142 -12
- data/tracks/c/exercises/pangram/src/{example.h → pangram.h} +0 -0
- data/tracks/c/exercises/space-age/.meta/hints.md +124 -0
- data/tracks/c/exercises/space-age/README.md +127 -2
- data/tracks/elisp/bin/test-examples +11 -14
- data/tracks/elisp/config.json +9 -1
- data/tracks/elisp/exercises/acronym/README.md +13 -0
- data/tracks/elisp/exercises/acronym/acronym-test.el +28 -0
- data/tracks/elisp/exercises/acronym/acronym.el +11 -0
- data/tracks/elisp/exercises/acronym/example.el +14 -0
- data/tracks/elisp/exercises/bob/example.el +1 -3
- data/tracks/elisp/exercises/hamming/README.md +2 -0
- data/tracks/elisp/exercises/hamming/hamming-test.el +35 -12
- data/tracks/elisp/exercises/hamming/hamming.el +0 -3
- data/tracks/fsharp/exercises/custom-set/CustomSetTest.fs +206 -132
- data/tracks/fsharp/exercises/custom-set/Example.fs +2 -0
- data/tracks/fsharp/generators/Generators.fs +61 -0
- data/tracks/go/config.json +25 -2
- data/tracks/go/exercises/acronym/acronym_test.go +8 -0
- data/tracks/go/exercises/alphametics/.meta/gen.go +72 -0
- data/tracks/go/exercises/alphametics/.meta/hints.md +38 -0
- data/tracks/go/exercises/alphametics/README.md +93 -0
- data/tracks/go/exercises/alphametics/alphametics_test.go +33 -0
- data/tracks/go/exercises/alphametics/cases_test.go +59 -0
- data/tracks/go/exercises/alphametics/example.go +212 -0
- data/tracks/go/exercises/atbash-cipher/.meta/gen.go +6 -4
- data/tracks/go/exercises/atbash-cipher/cases_test.go +2 -2
- data/tracks/go/exercises/change/.meta/gen.go +7 -5
- data/tracks/go/exercises/change/cases_test.go +2 -2
- data/tracks/go/exercises/collatz-conjecture/.meta/gen.go +5 -3
- data/tracks/go/exercises/collatz-conjecture/cases_test.go +2 -2
- data/tracks/go/exercises/connect/.meta/gen.go +5 -3
- data/tracks/go/exercises/connect/cases_test.go +2 -2
- data/tracks/go/exercises/flatten-array/.meta/gen.go +5 -3
- data/tracks/go/exercises/flatten-array/cases_test.go +2 -2
- data/tracks/go/exercises/meetup/.meta/gen.go +19 -8
- data/tracks/go/exercises/meetup/cases_test.go +2 -2
- data/tracks/go/exercises/simple-linked-list/README.md +47 -0
- data/tracks/go/exercises/simple-linked-list/example.go +74 -0
- data/tracks/go/exercises/simple-linked-list/linked_list_test.go +210 -0
- data/tracks/idris/config.json +2 -2
- data/tracks/java/exercises/all-your-base/.meta/src/reference/java/BaseConverter.java +1 -5
- data/tracks/java/exercises/all-your-base/.meta/version +1 -1
- data/tracks/java/exercises/all-your-base/src/test/java/BaseConverterTest.java +34 -10
- data/tracks/java/exercises/bob/README.md +2 -0
- data/tracks/java/exercises/kindergarten-garden/.meta/version +1 -0
- data/tracks/java/exercises/kindergarten-garden/src/test/java/KindergartenGardenTest.java +0 -55
- data/tracks/java/exercises/meetup/.meta/version +1 -0
- data/tracks/java/exercises/meetup/src/test/java/MeetupTest.java +32 -0
- data/tracks/java/exercises/parallel-letter-frequency/README.md +1 -0
- data/tracks/java/exercises/proverb/README.md +16 -10
- data/tracks/java/exercises/rail-fence-cipher/README.md +5 -6
- data/tracks/java/exercises/two-fer/README.md +1 -1
- data/tracks/java/exercises/word-search/.meta/version +1 -1
- data/tracks/java/exercises/word-search/src/test/java/WordSearcherTest.java +240 -2
- data/tracks/javascript/.eslintignore +0 -1
- data/tracks/javascript/exercises/palindrome-products/example.js +3 -3
- data/tracks/kotlin/docs/INSTALLATION.md +77 -68
- data/tracks/kotlin/docs/TESTS.md +41 -39
- data/tracks/perl6/exercises/acronym/acronym.t +20 -8
- data/tracks/perl6/exercises/acronym/example.yaml +1 -1
- data/tracks/perl6/exercises/all-your-base/all-your-base.t +108 -66
- data/tracks/perl6/exercises/all-your-base/example.yaml +2 -2
- data/tracks/perl6/exercises/allergies/allergies.t +39 -15
- data/tracks/perl6/exercises/allergies/example.yaml +2 -2
- data/tracks/perl6/exercises/anagram/anagram.t +73 -40
- data/tracks/perl6/exercises/anagram/example.yaml +1 -1
- data/tracks/perl6/exercises/atbash-cipher/atbash-cipher.t +38 -15
- data/tracks/perl6/exercises/atbash-cipher/example.yaml +1 -1
- data/tracks/perl6/exercises/bob/bob.t +77 -27
- data/tracks/perl6/exercises/bob/example.yaml +1 -1
- data/tracks/perl6/exercises/flatten-array/example.yaml +1 -1
- data/tracks/perl6/exercises/flatten-array/flatten-array.t +20 -8
- data/tracks/perl6/exercises/luhn/example.yaml +1 -1
- data/tracks/perl6/exercises/luhn/luhn.t +42 -16
- data/tracks/perl6/exercises/roman-numerals/README.md +68 -0
- data/tracks/php/exercises/gigasecond/gigasecond_test.php +1 -1
- data/tracks/python/docs/ABOUT.md +2 -2
- metadata +27 -10
- data/tracks/bash/docs/EXERCISE_README_INSERT.md +0 -3
- data/tracks/bash/exercises/word-count/example.awk +0 -12
- data/tracks/java/exercises/alphametics/src/main/java/.keep +0 -0
|
@@ -24,6 +24,8 @@ let intersection left right = left.items |> List.filter (fun x -> List.contains
|
|
|
24
24
|
|
|
25
25
|
let difference left right = left.items |> List.filter (fun x -> List.contains x right.items |> not) |> fromList
|
|
26
26
|
|
|
27
|
+
let isEqualTo left right = (size left = size right) && (isEmpty (difference left right))
|
|
28
|
+
|
|
27
29
|
let isSubsetOf left right = left.items |> List.forall (fun x -> List.contains x right.items)
|
|
28
30
|
|
|
29
31
|
let isDisjointFrom left right = left.items |> List.exists (fun x -> List.contains x right.items) |> not
|
|
@@ -227,6 +227,67 @@ type CollatzConjecture() =
|
|
|
227
227
|
type CryptoSquare() =
|
|
228
228
|
inherit GeneratorExercise()
|
|
229
229
|
|
|
230
|
+
type CustomSet() =
|
|
231
|
+
inherit GeneratorExercise()
|
|
232
|
+
|
|
233
|
+
member __.SutName = "actual"
|
|
234
|
+
|
|
235
|
+
override __.TestMethodBodyAssertTemplate _ = "AssertEqual"
|
|
236
|
+
|
|
237
|
+
member __.RenderSet canonicalDataCase key =
|
|
238
|
+
let value =
|
|
239
|
+
(canonicalDataCase.Properties.[key] :?> JToken).ToObject<seq<string>>()
|
|
240
|
+
|> formatList
|
|
241
|
+
sprintf "CustomSet.fromList %s" value
|
|
242
|
+
|
|
243
|
+
override this.RenderSut canonicalDataCase =
|
|
244
|
+
match canonicalDataCase.Property with
|
|
245
|
+
| "add" | "intersection" | "difference" | "union" ->
|
|
246
|
+
sprintf "%sBool" this.SutName
|
|
247
|
+
| _ -> this.SutName
|
|
248
|
+
|
|
249
|
+
override __.RenderExpected (canonicalDataCase, _, _) =
|
|
250
|
+
match canonicalDataCase.Property with
|
|
251
|
+
| "add" | "intersection" | "difference" | "union" ->
|
|
252
|
+
"true"
|
|
253
|
+
| _ -> formatValue canonicalDataCase.Expected
|
|
254
|
+
|
|
255
|
+
override this.RenderArrange canonicalDataCase =
|
|
256
|
+
let arrangeLines =
|
|
257
|
+
match canonicalDataCase.Property with
|
|
258
|
+
| "empty" ->
|
|
259
|
+
let setValue = this.RenderSet canonicalDataCase "set"
|
|
260
|
+
[ sprintf "let %s = CustomSet.isEmpty (%s)" this.SutName setValue ]
|
|
261
|
+
| "add" | "contains" ->
|
|
262
|
+
let methodName =
|
|
263
|
+
match canonicalDataCase.Property with
|
|
264
|
+
| "add" -> "insert"
|
|
265
|
+
| s -> s
|
|
266
|
+
let setVar = sprintf "let setValue = %s" (this.RenderSet canonicalDataCase "set")
|
|
267
|
+
let valueVar = sprintf "let element = %s" (formatValue canonicalDataCase.Properties.["element"])
|
|
268
|
+
let resultVar = sprintf "let %s = CustomSet.%s element setValue" this.SutName methodName
|
|
269
|
+
[ setVar; valueVar; resultVar ]
|
|
270
|
+
| "intersection" | "difference" | "union" | "disjoint" | "subset" | "equal" ->
|
|
271
|
+
let methodName =
|
|
272
|
+
match canonicalDataCase.Property with
|
|
273
|
+
| "disjoint" -> "isDisjointFrom"
|
|
274
|
+
| "subset" -> "isSubsetOf"
|
|
275
|
+
| "equal" -> "isEqualTo"
|
|
276
|
+
| s -> s
|
|
277
|
+
let firstSetVar = sprintf "let set1 = %s" (this.RenderSet canonicalDataCase "set1")
|
|
278
|
+
let secondSetVar = sprintf "let set2 = %s" (this.RenderSet canonicalDataCase "set2")
|
|
279
|
+
let resultVar = sprintf "let %s = CustomSet.%s set1 set2" this.SutName methodName
|
|
280
|
+
[ firstSetVar; secondSetVar; resultVar ]
|
|
281
|
+
| _ ->
|
|
282
|
+
[ "" ]
|
|
283
|
+
|
|
284
|
+
match canonicalDataCase.Property with
|
|
285
|
+
| "add" | "intersection" | "difference" | "union" ->
|
|
286
|
+
let expectedSetVar = sprintf "let expectedSet = %s" (this.RenderSet canonicalDataCase "expected")
|
|
287
|
+
let actualBoolVar = sprintf "let %sBool = CustomSet.isEqualTo %s expectedSet" this.SutName this.SutName
|
|
288
|
+
arrangeLines @ [ expectedSetVar; actualBoolVar ]
|
|
289
|
+
| _ -> arrangeLines
|
|
290
|
+
|
|
230
291
|
type Diamond() =
|
|
231
292
|
inherit CustomExercise()
|
|
232
293
|
|
data/tracks/go/config.json
CHANGED
|
@@ -870,6 +870,17 @@
|
|
|
870
870
|
"unlocked_by": "clock",
|
|
871
871
|
"uuid": "fcf735fe-a659-40ae-858e-6d1e834a4faf"
|
|
872
872
|
},
|
|
873
|
+
{
|
|
874
|
+
"core": false,
|
|
875
|
+
"difficulty": 4,
|
|
876
|
+
"slug": "simple-linked-list",
|
|
877
|
+
"topics": [
|
|
878
|
+
"arrays",
|
|
879
|
+
"loops"
|
|
880
|
+
],
|
|
881
|
+
"unlocked_by": "error-handling",
|
|
882
|
+
"uuid": "408f235e-f1ce-11e7-8c3f-9a214cf093ae"
|
|
883
|
+
},
|
|
873
884
|
{
|
|
874
885
|
"core": false,
|
|
875
886
|
"difficulty": 5,
|
|
@@ -1183,6 +1194,19 @@
|
|
|
1183
1194
|
"unlocked_by": "flatten-array",
|
|
1184
1195
|
"uuid": "2e5c9e76-decd-49db-be4b-353ebeb46b73"
|
|
1185
1196
|
},
|
|
1197
|
+
{
|
|
1198
|
+
"core": false,
|
|
1199
|
+
"difficulty": 5,
|
|
1200
|
+
"slug": "alphametics",
|
|
1201
|
+
"topics": [
|
|
1202
|
+
"algorithms",
|
|
1203
|
+
"arrays",
|
|
1204
|
+
"mathematics",
|
|
1205
|
+
"searching"
|
|
1206
|
+
],
|
|
1207
|
+
"unlocked_by": "flatten-array",
|
|
1208
|
+
"uuid": "6b313720-104a-46c2-8290-4b4af121101f "
|
|
1209
|
+
},
|
|
1186
1210
|
{
|
|
1187
1211
|
"deprecated": true,
|
|
1188
1212
|
"slug": "binary",
|
|
@@ -1210,8 +1234,7 @@
|
|
|
1210
1234
|
}
|
|
1211
1235
|
],
|
|
1212
1236
|
"foregone": [
|
|
1213
|
-
"linked-list"
|
|
1214
|
-
"simple-linked-list"
|
|
1237
|
+
"linked-list"
|
|
1215
1238
|
],
|
|
1216
1239
|
"ignore_pattern": "example(?!.*test)",
|
|
1217
1240
|
"language": "Go"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"log"
|
|
5
|
+
"sort"
|
|
6
|
+
"strings"
|
|
7
|
+
"text/template"
|
|
8
|
+
|
|
9
|
+
"../../../gen"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func main() {
|
|
13
|
+
t, err := template.New("").Parse(tmpl)
|
|
14
|
+
if err != nil {
|
|
15
|
+
log.Fatal(err)
|
|
16
|
+
}
|
|
17
|
+
var j js
|
|
18
|
+
if err := gen.Gen("alphametics", &j, t); err != nil {
|
|
19
|
+
log.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The JSON structure we expect to be able to unmarshal into
|
|
24
|
+
type js struct {
|
|
25
|
+
Cases []struct {
|
|
26
|
+
Description string
|
|
27
|
+
Cases []OneCase
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
type OneCase struct {
|
|
31
|
+
Description string
|
|
32
|
+
Input struct {
|
|
33
|
+
Puzzle string
|
|
34
|
+
}
|
|
35
|
+
Expected map[string]int
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func (c OneCase) ErrorExpected() bool {
|
|
39
|
+
return len(c.Expected) == 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func (c OneCase) SortedMapString() string {
|
|
43
|
+
strs := make([]string, 0, len(c.Expected))
|
|
44
|
+
for s, v := range c.Expected {
|
|
45
|
+
strs = append(strs, `"`+s+`": `+string(v+'0'))
|
|
46
|
+
}
|
|
47
|
+
sort.Strings(strs)
|
|
48
|
+
return strings.Join(strs, ",")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// template applied to above data structure generates the Go test cases
|
|
52
|
+
var tmpl = `package alphametics
|
|
53
|
+
|
|
54
|
+
{{.Header}}
|
|
55
|
+
|
|
56
|
+
{{range .J.Cases}}// {{.Description}}
|
|
57
|
+
var testCases = []struct {
|
|
58
|
+
description string
|
|
59
|
+
input string
|
|
60
|
+
expected map[string]int
|
|
61
|
+
errorExpected bool
|
|
62
|
+
}{
|
|
63
|
+
{{range .Cases}}{
|
|
64
|
+
description: {{printf "%q" .Description}},
|
|
65
|
+
input: {{printf "%q" .Input.Puzzle}},
|
|
66
|
+
{{if .ErrorExpected}}errorExpected: true,
|
|
67
|
+
{{else}}expected: map[string]int{ {{.SortedMapString}} },
|
|
68
|
+
{{- end}}
|
|
69
|
+
},
|
|
70
|
+
{{end}}{{end}}
|
|
71
|
+
}
|
|
72
|
+
`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
## Implementation
|
|
2
|
+
|
|
3
|
+
Define a single Go func, Solve, which accepts a puzzle string which may have zero
|
|
4
|
+
or more + operators, and one == operator; Solve should attempt to solve the alphametics puzzle
|
|
5
|
+
and return a map of all the letter substitutions for both the puzzle and the addition solution.
|
|
6
|
+
|
|
7
|
+
Use the following signature for func Solve:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
func Solve(puzzle string) (map[string]int, error) {
|
|
11
|
+
```
|
|
12
|
+
Solve should return an error if there is no solution to the given puzzle.
|
|
13
|
+
|
|
14
|
+
An example puzzle and returned solution is:
|
|
15
|
+
```
|
|
16
|
+
Solve("SEND + MORE == MONEY")
|
|
17
|
+
```
|
|
18
|
+
would return
|
|
19
|
+
```
|
|
20
|
+
map[string]int{"M":1, "O":0, "N":6, "E":5, "Y":2, "S":9, "D":7, "R":8}, nil
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
S E N D
|
|
25
|
+
M O R E +
|
|
26
|
+
-----------
|
|
27
|
+
M O N E Y
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Replacing these with valid numbers gives:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
9 5 6 7
|
|
34
|
+
1 0 8 5 +
|
|
35
|
+
-----------
|
|
36
|
+
1 0 6 5 2
|
|
37
|
+
```
|
|
38
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Alphametics
|
|
2
|
+
|
|
3
|
+
Write a function to solve alphametics puzzles.
|
|
4
|
+
|
|
5
|
+
[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where
|
|
6
|
+
letters in words are replaced with numbers.
|
|
7
|
+
|
|
8
|
+
For example `SEND + MORE = MONEY`:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
S E N D
|
|
12
|
+
M O R E +
|
|
13
|
+
-----------
|
|
14
|
+
M O N E Y
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Replacing these with valid numbers gives:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
9 5 6 7
|
|
21
|
+
1 0 8 5 +
|
|
22
|
+
-----------
|
|
23
|
+
1 0 6 5 2
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This is correct because every letter is replaced by a different number and the
|
|
27
|
+
words, translated into numbers, then make a valid sum.
|
|
28
|
+
|
|
29
|
+
Each letter must represent a different digit, and the leading digit of
|
|
30
|
+
a multi-digit number must not be zero.
|
|
31
|
+
|
|
32
|
+
Write a function to solve alphametics puzzles.
|
|
33
|
+
|
|
34
|
+
## Implementation
|
|
35
|
+
|
|
36
|
+
Define a single Go func, Solve, which accepts a puzzle string which may have zero
|
|
37
|
+
or more + operators, and one == operator; Solve should attempt to solve the alphametics puzzle
|
|
38
|
+
and return a map of all the letter substitutions for both the puzzle and the addition solution.
|
|
39
|
+
|
|
40
|
+
Use the following signature for func Solve:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
func Solve(puzzle string) (map[string]int, error) {
|
|
44
|
+
```
|
|
45
|
+
Solve should return an error if there is no solution to the given puzzle.
|
|
46
|
+
|
|
47
|
+
An example puzzle and returned solution is:
|
|
48
|
+
```
|
|
49
|
+
Solve("SEND + MORE == MONEY")
|
|
50
|
+
```
|
|
51
|
+
would return
|
|
52
|
+
```
|
|
53
|
+
map[string]int{"M":1, "O":0, "N":6, "E":5, "Y":2, "S":9, "D":7, "R":8}, nil
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
S E N D
|
|
58
|
+
M O R E +
|
|
59
|
+
-----------
|
|
60
|
+
M O N E Y
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Replacing these with valid numbers gives:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
9 5 6 7
|
|
67
|
+
1 0 8 5 +
|
|
68
|
+
-----------
|
|
69
|
+
1 0 6 5 2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Running the tests
|
|
75
|
+
|
|
76
|
+
To run the tests run the command `go test` from within the exercise directory.
|
|
77
|
+
|
|
78
|
+
If the test suite contains benchmarks, you can run these with the `-bench`
|
|
79
|
+
flag:
|
|
80
|
+
|
|
81
|
+
go test -bench .
|
|
82
|
+
|
|
83
|
+
Keep in mind that each reviewer will run benchmarks on a different machine, with
|
|
84
|
+
different specs, so the results from these benchmark tests may vary.
|
|
85
|
+
|
|
86
|
+
## Further information
|
|
87
|
+
|
|
88
|
+
For more detailed information about the Go track, including how to get help if
|
|
89
|
+
you're having trouble, please visit the exercism.io [Go language page](http://exercism.io/languages/go/about).
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Submitting Incomplete Solutions
|
|
93
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package alphametics
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"reflect"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestSolve(t *testing.T) {
|
|
9
|
+
for _, tc := range testCases {
|
|
10
|
+
s, err := Solve(tc.input)
|
|
11
|
+
if tc.errorExpected {
|
|
12
|
+
if err == nil {
|
|
13
|
+
t.Fatalf("FAIL: %s\nSolve(%q)\nExpected error\nActual: %#v",
|
|
14
|
+
tc.description, tc.input, s)
|
|
15
|
+
}
|
|
16
|
+
} else if err != nil {
|
|
17
|
+
t.Fatalf("FAIL: %s\nSolve(%q)\nExpected: %#v\nGot error: %q",
|
|
18
|
+
tc.description, tc.input, tc.expected, err)
|
|
19
|
+
} else if !reflect.DeepEqual(s, tc.expected) {
|
|
20
|
+
t.Fatalf("FAIL: %s\nSolve(%q)\nExpected: %#v\nActual: %#v",
|
|
21
|
+
tc.description, tc.input, tc.expected, s)
|
|
22
|
+
}
|
|
23
|
+
t.Logf("PASS: %s", tc.description)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func BenchmarkSolve(b *testing.B) {
|
|
28
|
+
for i := 0; i < b.N; i++ {
|
|
29
|
+
for _, tc := range testCases {
|
|
30
|
+
Solve(tc.input)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package alphametics
|
|
2
|
+
|
|
3
|
+
// Source: exercism/problem-specifications
|
|
4
|
+
// Commit: a86a774 alphametics: apply input policy to final test case
|
|
5
|
+
// Problem Specifications Version: 1.2.0
|
|
6
|
+
|
|
7
|
+
// Solve the alphametics puzzle
|
|
8
|
+
var testCases = []struct {
|
|
9
|
+
description string
|
|
10
|
+
input string
|
|
11
|
+
expected map[string]int
|
|
12
|
+
errorExpected bool
|
|
13
|
+
}{
|
|
14
|
+
{
|
|
15
|
+
description: "puzzle with three letters",
|
|
16
|
+
input: "I + BB == ILL",
|
|
17
|
+
expected: map[string]int{"B": 9, "I": 1, "L": 0},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
description: "solution must have unique value for each letter",
|
|
21
|
+
input: "A == B",
|
|
22
|
+
errorExpected: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
description: "leading zero solution is invalid",
|
|
26
|
+
input: "ACA + DD == BD",
|
|
27
|
+
errorExpected: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
description: "puzzle with four letters",
|
|
31
|
+
input: "AS + A == MOM",
|
|
32
|
+
expected: map[string]int{"A": 9, "M": 1, "O": 0, "S": 2},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
description: "puzzle with six letters",
|
|
36
|
+
input: "NO + NO + TOO == LATE",
|
|
37
|
+
expected: map[string]int{"A": 0, "E": 2, "L": 1, "N": 7, "O": 4, "T": 9},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
description: "puzzle with seven letters",
|
|
41
|
+
input: "HE + SEES + THE == LIGHT",
|
|
42
|
+
expected: map[string]int{"E": 4, "G": 2, "H": 5, "I": 0, "L": 1, "S": 9, "T": 7},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
description: "puzzle with eight letters",
|
|
46
|
+
input: "SEND + MORE == MONEY",
|
|
47
|
+
expected: map[string]int{"D": 7, "E": 5, "M": 1, "N": 6, "O": 0, "R": 8, "S": 9, "Y": 2},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
description: "puzzle with ten letters",
|
|
51
|
+
input: "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE",
|
|
52
|
+
expected: map[string]int{"A": 5, "D": 3, "E": 4, "F": 7, "G": 8, "N": 0, "O": 2, "R": 1, "S": 6, "T": 9},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
description: "puzzle with ten letters and 199 addends",
|
|
56
|
+
input: "THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES",
|
|
57
|
+
expected: map[string]int{"A": 1, "E": 0, "F": 5, "H": 8, "I": 7, "L": 2, "O": 6, "R": 3, "S": 4, "T": 9},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
package alphametics
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"strings"
|
|
6
|
+
"unicode"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
var decDigits = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
10
|
+
|
|
11
|
+
type problem struct {
|
|
12
|
+
vDigits [][]rune
|
|
13
|
+
maxDigits int
|
|
14
|
+
letterValues [26]int
|
|
15
|
+
lettersUsed []rune
|
|
16
|
+
nLetters int
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func Solve(puzzle string) (map[string]int, error) {
|
|
20
|
+
p := parsePuzzle(puzzle)
|
|
21
|
+
if p == nil {
|
|
22
|
+
return nil, errors.New("invalid puzzle")
|
|
23
|
+
}
|
|
24
|
+
return p.solvePuzzle()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// parsePuzzle parses the puzzle input into a problem for solving.
|
|
28
|
+
func parsePuzzle(puzzle string) (p *problem) {
|
|
29
|
+
var valueStrings []string
|
|
30
|
+
p = new(problem)
|
|
31
|
+
fields := strings.Fields(puzzle)
|
|
32
|
+
for _, field := range fields {
|
|
33
|
+
if field == "+" || field == "==" {
|
|
34
|
+
continue
|
|
35
|
+
} else {
|
|
36
|
+
valueStrings = append(valueStrings, field)
|
|
37
|
+
if len(field) > p.maxDigits {
|
|
38
|
+
p.maxDigits = len(field)
|
|
39
|
+
}
|
|
40
|
+
for _, r := range field {
|
|
41
|
+
if !unicode.IsUpper(r) {
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
v := r - 'A'
|
|
45
|
+
p.letterValues[v] = -1
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Count the letters used.
|
|
50
|
+
for v := 0; v < len(p.letterValues); v++ {
|
|
51
|
+
if p.letterValues[v] == -1 {
|
|
52
|
+
p.nLetters++
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
p.vDigits = make([][]rune, len(valueStrings))
|
|
56
|
+
for i := range valueStrings {
|
|
57
|
+
p.vDigits[i] = make([]rune, p.maxDigits)
|
|
58
|
+
for d, r := range valueStrings[i] {
|
|
59
|
+
j := len(valueStrings[i]) - 1 - d
|
|
60
|
+
// Each vDigits value is 0 for nothing in this position,
|
|
61
|
+
// or 1..26 for 'A'..'Z'.
|
|
62
|
+
p.vDigits[i][j] = r - 'A' + 1
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Make a list of the letters used, where 0 == 'A'
|
|
66
|
+
p.lettersUsed = make([]rune, p.nLetters)
|
|
67
|
+
for n, v := 0, 0; v < len(p.letterValues); v++ {
|
|
68
|
+
if p.letterValues[v] == -1 {
|
|
69
|
+
p.lettersUsed[n] = rune(v) // 0 == 'A'
|
|
70
|
+
n++
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return p
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func (p *problem) solvePuzzle() (map[string]int, error) {
|
|
77
|
+
for _, digValues := range permutations(decDigits, p.nLetters) {
|
|
78
|
+
if p.isPuzzleSolution(digValues) {
|
|
79
|
+
// Check leading digit of answer for 0 (invalid solution).
|
|
80
|
+
r := p.vDigits[len(p.vDigits)-1][p.maxDigits-1]
|
|
81
|
+
if p.letterValues[r-1] == 0 {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
return p.puzzleMap(), nil
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return nil, errors.New("no solution")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// isPuzzleSolution returns true if the values work out as a solution.
|
|
91
|
+
func (p *problem) isPuzzleSolution(values []int) bool {
|
|
92
|
+
// Put the candidate values into the letterValues for the lettersUsed.
|
|
93
|
+
for i, r := range p.lettersUsed {
|
|
94
|
+
p.letterValues[r] = values[i]
|
|
95
|
+
}
|
|
96
|
+
// For each column up to maxDigits
|
|
97
|
+
// check that the sum of the digits corresponding to values in vDigits
|
|
98
|
+
// add up to the digits (modulo 10) of values in last row.
|
|
99
|
+
carry := 0
|
|
100
|
+
for d := 0; d < p.maxDigits; d++ {
|
|
101
|
+
// Get initial sum for the digits for the this column of vDigits
|
|
102
|
+
r := p.vDigits[0][d]
|
|
103
|
+
sum := carry
|
|
104
|
+
if r != 0 {
|
|
105
|
+
// There's a character in this position.
|
|
106
|
+
sum += p.letterValues[r-1]
|
|
107
|
+
}
|
|
108
|
+
// Sum remaining rows for this column.
|
|
109
|
+
for n := 1; n < len(p.vDigits)-1; n++ {
|
|
110
|
+
r = p.vDigits[n][d]
|
|
111
|
+
if r != 0 {
|
|
112
|
+
// There's a character in this position.
|
|
113
|
+
sum += p.letterValues[r-1]
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
carry = sum / 10
|
|
117
|
+
sum = sum % 10
|
|
118
|
+
// Check the result sum against the answer row digit.
|
|
119
|
+
r = p.vDigits[len(p.vDigits)-1][d]
|
|
120
|
+
if r == 0 || sum != p.letterValues[r-1] {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// puzzleMap creates a "by letter" map from the letterValues used.
|
|
128
|
+
func (p *problem) puzzleMap() map[string]int {
|
|
129
|
+
pm := make(map[string]int, p.nLetters)
|
|
130
|
+
for _, v := range p.lettersUsed {
|
|
131
|
+
r := v + 'A'
|
|
132
|
+
s := string(r)
|
|
133
|
+
pm[s] = p.letterValues[v]
|
|
134
|
+
}
|
|
135
|
+
return pm
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// permutations returns a slice containing the r length permutations of the elements in iterable.
|
|
139
|
+
// The implementation is modeled after the Python itertools.permutations().
|
|
140
|
+
func permutations(iterable []int, r int) (perms [][]int) {
|
|
141
|
+
pool := iterable
|
|
142
|
+
n := len(pool)
|
|
143
|
+
nperm := 1
|
|
144
|
+
for i := n; i > 1; i-- {
|
|
145
|
+
nperm *= i
|
|
146
|
+
}
|
|
147
|
+
if r < n {
|
|
148
|
+
d := 1
|
|
149
|
+
for i := (n - r); i > 1; i-- {
|
|
150
|
+
d *= i
|
|
151
|
+
}
|
|
152
|
+
nperm = nperm / d
|
|
153
|
+
}
|
|
154
|
+
perms = make([][]int, 0, nperm)
|
|
155
|
+
|
|
156
|
+
if r > n {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
indices := make([]int, n)
|
|
161
|
+
for i := range indices {
|
|
162
|
+
indices[i] = i
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
cycles := make([]int, r)
|
|
166
|
+
for i := range cycles {
|
|
167
|
+
cycles[i] = n - i
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
result := make([]int, r)
|
|
171
|
+
for i, el := range indices[:r] {
|
|
172
|
+
result[i] = pool[el]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
p := make([]int, len(result))
|
|
176
|
+
copy(p, result)
|
|
177
|
+
perms = append(perms, p)
|
|
178
|
+
|
|
179
|
+
for n > 0 {
|
|
180
|
+
i := r - 1
|
|
181
|
+
for ; i >= 0; i-- {
|
|
182
|
+
cycles[i]--
|
|
183
|
+
if cycles[i] == 0 {
|
|
184
|
+
index := indices[i]
|
|
185
|
+
for j := i; j < n-1; j++ {
|
|
186
|
+
indices[j] = indices[j+1]
|
|
187
|
+
}
|
|
188
|
+
indices[n-1] = index
|
|
189
|
+
cycles[i] = n - i
|
|
190
|
+
} else {
|
|
191
|
+
j := cycles[i]
|
|
192
|
+
indices[i], indices[n-j] = indices[n-j], indices[i]
|
|
193
|
+
|
|
194
|
+
for k := i; k < r; k++ {
|
|
195
|
+
result[k] = pool[indices[k]]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
p = make([]int, len(result))
|
|
199
|
+
copy(p, result)
|
|
200
|
+
perms = append(perms, p)
|
|
201
|
+
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if i < 0 {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
return
|
|
212
|
+
}
|