trackler 2.2.1.86 → 2.2.1.87
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|