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.
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
+ }