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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trackler/version.rb +1 -1
  3. data/problem-specifications/exercises/reverse-string/metadata.yml +0 -1
  4. data/tracks/bash/CONTRIBUTING.md +129 -28
  5. data/tracks/bash/config/exercise_readme.go.tmpl +10 -4
  6. data/tracks/bash/exercises/acronym/README.md +8 -11
  7. data/tracks/bash/exercises/acronym/{acronym_tests.sh → acronym_test.sh} +0 -0
  8. data/tracks/bash/exercises/anagram/README.md +6 -1
  9. data/tracks/bash/exercises/anagram/anagram_test.sh +1 -1
  10. data/tracks/bash/exercises/armstrong-numbers/README.md +10 -2
  11. data/tracks/bash/exercises/atbash-cipher/README.md +6 -6
  12. data/tracks/bash/exercises/bob/README.md +8 -1
  13. data/tracks/bash/exercises/collatz-conjecture/README.md +11 -2
  14. data/tracks/bash/exercises/collatz-conjecture/{collatz_test.sh → collatz_conjecture_test.sh} +6 -6
  15. data/tracks/bash/exercises/difference-of-squares/README.md +6 -1
  16. data/tracks/bash/exercises/error-handling/README.md +13 -5
  17. data/tracks/bash/exercises/error-handling/error_handling_test.sh +18 -22
  18. data/tracks/bash/exercises/error-handling/example.sh +7 -6
  19. data/tracks/bash/exercises/gigasecond/.meta/hints.md +0 -5
  20. data/tracks/bash/exercises/gigasecond/README.md +36 -1
  21. data/tracks/bash/exercises/grains/README.md +10 -2
  22. data/tracks/bash/exercises/hamming/README.md +6 -1
  23. data/tracks/bash/exercises/hello-world/README.md +7 -2
  24. data/tracks/bash/exercises/leap/README.md +7 -2
  25. data/tracks/bash/exercises/luhn/README.md +7 -2
  26. data/tracks/bash/exercises/nucleotide-count/README.md +13 -22
  27. data/tracks/bash/exercises/pangram/README.md +7 -2
  28. data/tracks/bash/exercises/phone-number/README.md +10 -4
  29. data/tracks/bash/exercises/raindrops/README.md +6 -1
  30. data/tracks/bash/exercises/reverse-string/README.md +11 -2
  31. data/tracks/bash/exercises/rna-transcription/README.md +7 -2
  32. data/tracks/bash/exercises/roman-numerals/README.md +11 -2
  33. data/tracks/bash/exercises/triangle/README.md +5 -4
  34. data/tracks/bash/exercises/two-fer/README.md +7 -9
  35. data/tracks/bash/exercises/word-count/README.md +6 -2
  36. data/tracks/bash/exercises/word-count/example.sh +22 -13
  37. data/tracks/bash/scripts/canonical_data_check.sh +112 -0
  38. data/tracks/c/exercises/acronym/src/{example.h → acronym.h} +0 -0
  39. data/tracks/c/exercises/gigasecond/.meta/hints.md +124 -0
  40. data/tracks/c/exercises/gigasecond/README.md +126 -0
  41. data/tracks/c/exercises/isogram/src/{example.h → isogram.h} +0 -0
  42. data/tracks/c/exercises/meetup/.meta/hints.md +124 -0
  43. data/tracks/c/exercises/meetup/README.md +142 -12
  44. data/tracks/c/exercises/pangram/src/{example.h → pangram.h} +0 -0
  45. data/tracks/c/exercises/space-age/.meta/hints.md +124 -0
  46. data/tracks/c/exercises/space-age/README.md +127 -2
  47. data/tracks/elisp/bin/test-examples +11 -14
  48. data/tracks/elisp/config.json +9 -1
  49. data/tracks/elisp/exercises/acronym/README.md +13 -0
  50. data/tracks/elisp/exercises/acronym/acronym-test.el +28 -0
  51. data/tracks/elisp/exercises/acronym/acronym.el +11 -0
  52. data/tracks/elisp/exercises/acronym/example.el +14 -0
  53. data/tracks/elisp/exercises/bob/example.el +1 -3
  54. data/tracks/elisp/exercises/hamming/README.md +2 -0
  55. data/tracks/elisp/exercises/hamming/hamming-test.el +35 -12
  56. data/tracks/elisp/exercises/hamming/hamming.el +0 -3
  57. data/tracks/fsharp/exercises/custom-set/CustomSetTest.fs +206 -132
  58. data/tracks/fsharp/exercises/custom-set/Example.fs +2 -0
  59. data/tracks/fsharp/generators/Generators.fs +61 -0
  60. data/tracks/go/config.json +25 -2
  61. data/tracks/go/exercises/acronym/acronym_test.go +8 -0
  62. data/tracks/go/exercises/alphametics/.meta/gen.go +72 -0
  63. data/tracks/go/exercises/alphametics/.meta/hints.md +38 -0
  64. data/tracks/go/exercises/alphametics/README.md +93 -0
  65. data/tracks/go/exercises/alphametics/alphametics_test.go +33 -0
  66. data/tracks/go/exercises/alphametics/cases_test.go +59 -0
  67. data/tracks/go/exercises/alphametics/example.go +212 -0
  68. data/tracks/go/exercises/atbash-cipher/.meta/gen.go +6 -4
  69. data/tracks/go/exercises/atbash-cipher/cases_test.go +2 -2
  70. data/tracks/go/exercises/change/.meta/gen.go +7 -5
  71. data/tracks/go/exercises/change/cases_test.go +2 -2
  72. data/tracks/go/exercises/collatz-conjecture/.meta/gen.go +5 -3
  73. data/tracks/go/exercises/collatz-conjecture/cases_test.go +2 -2
  74. data/tracks/go/exercises/connect/.meta/gen.go +5 -3
  75. data/tracks/go/exercises/connect/cases_test.go +2 -2
  76. data/tracks/go/exercises/flatten-array/.meta/gen.go +5 -3
  77. data/tracks/go/exercises/flatten-array/cases_test.go +2 -2
  78. data/tracks/go/exercises/meetup/.meta/gen.go +19 -8
  79. data/tracks/go/exercises/meetup/cases_test.go +2 -2
  80. data/tracks/go/exercises/simple-linked-list/README.md +47 -0
  81. data/tracks/go/exercises/simple-linked-list/example.go +74 -0
  82. data/tracks/go/exercises/simple-linked-list/linked_list_test.go +210 -0
  83. data/tracks/idris/config.json +2 -2
  84. data/tracks/java/exercises/all-your-base/.meta/src/reference/java/BaseConverter.java +1 -5
  85. data/tracks/java/exercises/all-your-base/.meta/version +1 -1
  86. data/tracks/java/exercises/all-your-base/src/test/java/BaseConverterTest.java +34 -10
  87. data/tracks/java/exercises/bob/README.md +2 -0
  88. data/tracks/java/exercises/kindergarten-garden/.meta/version +1 -0
  89. data/tracks/java/exercises/kindergarten-garden/src/test/java/KindergartenGardenTest.java +0 -55
  90. data/tracks/java/exercises/meetup/.meta/version +1 -0
  91. data/tracks/java/exercises/meetup/src/test/java/MeetupTest.java +32 -0
  92. data/tracks/java/exercises/parallel-letter-frequency/README.md +1 -0
  93. data/tracks/java/exercises/proverb/README.md +16 -10
  94. data/tracks/java/exercises/rail-fence-cipher/README.md +5 -6
  95. data/tracks/java/exercises/two-fer/README.md +1 -1
  96. data/tracks/java/exercises/word-search/.meta/version +1 -1
  97. data/tracks/java/exercises/word-search/src/test/java/WordSearcherTest.java +240 -2
  98. data/tracks/javascript/.eslintignore +0 -1
  99. data/tracks/javascript/exercises/palindrome-products/example.js +3 -3
  100. data/tracks/kotlin/docs/INSTALLATION.md +77 -68
  101. data/tracks/kotlin/docs/TESTS.md +41 -39
  102. data/tracks/perl6/exercises/acronym/acronym.t +20 -8
  103. data/tracks/perl6/exercises/acronym/example.yaml +1 -1
  104. data/tracks/perl6/exercises/all-your-base/all-your-base.t +108 -66
  105. data/tracks/perl6/exercises/all-your-base/example.yaml +2 -2
  106. data/tracks/perl6/exercises/allergies/allergies.t +39 -15
  107. data/tracks/perl6/exercises/allergies/example.yaml +2 -2
  108. data/tracks/perl6/exercises/anagram/anagram.t +73 -40
  109. data/tracks/perl6/exercises/anagram/example.yaml +1 -1
  110. data/tracks/perl6/exercises/atbash-cipher/atbash-cipher.t +38 -15
  111. data/tracks/perl6/exercises/atbash-cipher/example.yaml +1 -1
  112. data/tracks/perl6/exercises/bob/bob.t +77 -27
  113. data/tracks/perl6/exercises/bob/example.yaml +1 -1
  114. data/tracks/perl6/exercises/flatten-array/example.yaml +1 -1
  115. data/tracks/perl6/exercises/flatten-array/flatten-array.t +20 -8
  116. data/tracks/perl6/exercises/luhn/example.yaml +1 -1
  117. data/tracks/perl6/exercises/luhn/luhn.t +42 -16
  118. data/tracks/perl6/exercises/roman-numerals/README.md +68 -0
  119. data/tracks/php/exercises/gigasecond/gigasecond_test.php +1 -1
  120. data/tracks/python/docs/ABOUT.md +2 -2
  121. metadata +27 -10
  122. data/tracks/bash/docs/EXERCISE_README_INSERT.md +0 -3
  123. data/tracks/bash/exercises/word-count/example.awk +0 -12
  124. 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
 
@@ -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"
@@ -12,3 +12,11 @@ func TestAcronym(t *testing.T) {
12
12
  }
13
13
  }
14
14
  }
15
+
16
+ func BenchmarkAcronym(b *testing.B) {
17
+ for i := 0; i < b.N; i++ {
18
+ for _, test := range stringTestCases {
19
+ Abbreviate(test.input)
20
+ }
21
+ }
22
+ }
@@ -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
+ }