trackler 2.1.0.28 → 2.1.0.29

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 (27) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trackler/version.rb +1 -1
  3. data/tracks/ceylon/README.md +1 -1
  4. data/tracks/java/config.json +5 -0
  5. data/tracks/java/exercises/settings.gradle +1 -0
  6. data/tracks/java/exercises/word-search/build.gradle +18 -0
  7. data/tracks/java/exercises/word-search/src/example/java/Pair.java +37 -0
  8. data/tracks/java/exercises/word-search/src/example/java/WordLocation.java +29 -0
  9. data/tracks/java/exercises/word-search/src/example/java/WordSearcher.java +94 -0
  10. data/tracks/java/exercises/word-search/src/main/java/Pair.java +37 -0
  11. data/tracks/java/exercises/word-search/src/main/java/WordLocation.java +29 -0
  12. data/tracks/java/exercises/word-search/src/test/java/WordSearcherTest.java +278 -0
  13. data/tracks/scala/README.md +4 -7
  14. data/tracks/scala/config.json +11 -10
  15. data/tracks/scala/exercises/beer-song/src/test/scala/BeerSongTest.scala +2 -1
  16. data/tracks/scala/exercises/perfect-numbers/example.scala +14 -10
  17. data/tracks/scala/exercises/perfect-numbers/src/test/scala/PerfectNumbersTest.scala +34 -27
  18. data/tracks/scala/testgen/src/main/scala/BeerSongTestGenerator.scala +2 -0
  19. data/tracks/scala/testgen/src/main/scala/PerfectNumbersTestGenerator.scala +42 -0
  20. data/tracks/scala/testgen/src/main/scala/testgen/CanonicalDataParser.scala +1 -1
  21. data/tracks/scala/testgen/src/main/scala/testgen/TestSuiteBuilder.scala +2 -2
  22. data/tracks/vimscript/config.json +6 -2
  23. data/tracks/vimscript/exercises/etl/etl.vader +27 -0
  24. data/tracks/vimscript/exercises/etl/etl.vim +12 -0
  25. data/tracks/vimscript/exercises/etl/example.vim +11 -0
  26. metadata +13 -3
  27. data/tracks/scala/exercises/perfect-numbers/src/main/scala/PerfectNumbers.scala +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22277589d4cc0a204319b33cc82d7a7c0dac30b4
4
- data.tar.gz: 97204abac4f271dbe405bd4fd0f87dea116478b2
3
+ metadata.gz: 73dfe16b1709780cb1c87bc8a36da3a089c2ea4e
4
+ data.tar.gz: 41508819e3d7596fc565fad1029ca178ada8d123
5
5
  SHA512:
6
- metadata.gz: 0b5e630d56cdd344ec4b8f6e8003979a0bc82271373f95fe670d75f2ce4349e167ff6be6239492808859d29e007a502e8c1ec510a3bdafac55c38be864f7e389
7
- data.tar.gz: 8a1af1baa53f7457fcb6f2c1c87448d3573328554dcaf9ccbff79dad66632d1bffb5db3598e93369efe7668470462b2e84ce1127bd4f79ef49dec402680e603d
6
+ metadata.gz: 2c2a66c4a2a066f2a5fa9655a3ec3c9ee78d0ef560f9fa6b3ae7e0d1369f4c40a02cdb61c757079709cc87ec1fb0039b0cc1e50452b482e26c8869aeafc96121
7
+ data.tar.gz: 2c1dd36f3e6189b96fc37dac36f2bb23e3802f74abe75383547f5f5962ed18d20269a5a05dc4ec163ab077ef128562aa59893e1ec49d5aa89e4420920cd4ffd5
@@ -1,3 +1,3 @@
1
1
  module Trackler
2
- VERSION = "2.1.0.28"
2
+ VERSION = "2.1.0.29"
3
3
  end
@@ -13,7 +13,7 @@ You can just ask in the [Exercism Gitter support](https://gitter.im/exercism/sup
13
13
 
14
14
  ### How to contribute
15
15
 
16
- The Exercism-wide [contributing guide](https://github.com/exercism/x-common/blob/master/CONTRIBUTING.md) covers topics relevant to contributing to the entire Exercism project, including but not limited to the Ceylon track.
16
+ The Exercism-wide [contributing guide](https://github.com/exercism/docs/tree/master/contributing-to-language-tracks) covers topics relevant to contributing to the entire Exercism project, including but not limited to the Ceylon track.
17
17
 
18
18
  * To report a bug or ask a question, [create an issue](https://help.github.com/articles/creating-an-issue/).
19
19
  * If you already have a fix for a bug, you could [create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/).
@@ -255,6 +255,11 @@
255
255
  "difficulty": 7,
256
256
  "topics": []
257
257
  },
258
+ {
259
+ "slug": "word-search",
260
+ "difficulty": 7,
261
+ "topics": []
262
+ },
258
263
  {
259
264
  "slug": "simple-linked-list",
260
265
  "difficulty": 7,
@@ -69,4 +69,5 @@ include 'triangle'
69
69
  include 'trinary'
70
70
  include 'twelve-days'
71
71
  include 'word-count'
72
+ include 'word-search'
72
73
  include 'wordy'
@@ -0,0 +1,18 @@
1
+ apply plugin: "java"
2
+ apply plugin: "eclipse"
3
+ apply plugin: "idea"
4
+
5
+ repositories {
6
+ mavenCentral()
7
+ }
8
+
9
+ dependencies {
10
+ testCompile "junit:junit:4.12"
11
+ }
12
+
13
+ test {
14
+ testLogging {
15
+ exceptionFormat = 'full'
16
+ events = ["passed", "failed", "skipped"]
17
+ }
18
+ }
@@ -0,0 +1,37 @@
1
+ class Pair {
2
+
3
+ private final int x;
4
+
5
+ private final int y;
6
+
7
+ Pair(final int x, final int y) {
8
+ this.y = y;
9
+ this.x = x;
10
+ }
11
+
12
+ int getX() {
13
+ return x;
14
+ }
15
+
16
+ int getY() {
17
+ return y;
18
+ }
19
+
20
+ @Override
21
+ public boolean equals(final Object o) {
22
+ if (this == o) return true;
23
+ if (o == null || getClass() != o.getClass()) return false;
24
+
25
+ Pair pair = (Pair) o;
26
+
27
+ return x == pair.x && y == pair.y;
28
+ }
29
+
30
+ @Override
31
+ public int hashCode() {
32
+ int result = x;
33
+ result = 31 * result + y;
34
+ return result;
35
+ }
36
+
37
+ }
@@ -0,0 +1,29 @@
1
+ class WordLocation {
2
+
3
+ private final Pair startCoord;
4
+
5
+ private final Pair endCoord;
6
+
7
+ WordLocation(final Pair startCoord, final Pair endCoord) {
8
+ this.startCoord = startCoord;
9
+ this.endCoord = endCoord;
10
+ }
11
+
12
+ @Override
13
+ public boolean equals(final Object o) {
14
+ if (this == o) return true;
15
+ if (o == null || getClass() != o.getClass()) return false;
16
+
17
+ WordLocation that = (WordLocation) o;
18
+
19
+ return startCoord.equals(that.startCoord) && endCoord.equals(that.endCoord);
20
+ }
21
+
22
+ @Override
23
+ public int hashCode() {
24
+ int result = startCoord.hashCode();
25
+ result = 31 * result + endCoord.hashCode();
26
+ return result;
27
+ }
28
+
29
+ }
@@ -0,0 +1,94 @@
1
+ import java.util.*;
2
+ import java.util.function.Function;
3
+ import java.util.stream.Collectors;
4
+
5
+ class WordSearcher {
6
+
7
+ private static final List<Pair> DIRECTIONS = Arrays.asList(
8
+ new Pair( 1, 0), // N
9
+ new Pair( 1, 1), // NE
10
+ new Pair( 0, 1), // E
11
+ new Pair(-1, 1), // SE
12
+ new Pair(-1, 0), // S
13
+ new Pair(-1, -1), // SW
14
+ new Pair( 0, -1), // W
15
+ new Pair( 1, -1)); // NW
16
+
17
+ /*
18
+ * Search for multiple words in the given grid.
19
+ */
20
+ Map<String, Optional<WordLocation>> search(final Set<String> words, final char[][] grid) {
21
+ return words
22
+ .stream()
23
+ .collect(Collectors.toMap(Function.identity(), s -> search(s, grid)));
24
+ }
25
+
26
+ /*
27
+ * Search for a single word in the given grid.
28
+ */
29
+ private Optional<WordLocation> search(final CharSequence word, final char[][] grid) {
30
+ final int nRows = grid.length;
31
+ final int nCols = grid[0].length;
32
+
33
+ // 1-indexed
34
+ for (int c = 1; c <= nCols; c++) {
35
+ for (int r = 1; r <= nRows; r++) {
36
+ final Optional<WordLocation> wordLocation = search(word, grid, new Pair(c, r));
37
+ if (wordLocation.isPresent()) return wordLocation;
38
+ }
39
+ }
40
+
41
+ return Optional.empty();
42
+ }
43
+
44
+ /*
45
+ * Search for a single word starting at a given coordinate within the given grid.
46
+ */
47
+ private Optional<WordLocation> search(final CharSequence word, final char[][] grid, final Pair startCoord) {
48
+ if (grid[startCoord.getY() - 1][startCoord.getX() - 1] != word.charAt(0)) return Optional.empty();
49
+
50
+ for (final Pair direction : DIRECTIONS) {
51
+ final Optional<WordLocation> wordLocation = check(word, grid, startCoord, direction);
52
+ if (wordLocation.isPresent()) return wordLocation;
53
+ }
54
+
55
+ return Optional.empty();
56
+ }
57
+
58
+ /*
59
+ * Check whether a single word starts at a given coordinate and is aligned in a given direction within the given
60
+ * grid.
61
+ */
62
+ private Optional<WordLocation> check(
63
+ final CharSequence word,
64
+ final char[][] grid,
65
+ final Pair startCoord,
66
+ final Pair direction) {
67
+
68
+ final int nRows = grid.length;
69
+ final int nCols = grid[0].length;
70
+
71
+ Pair nextCoord = startCoord;
72
+
73
+ for (int charIndex = 1; charIndex < word.length(); charIndex++) {
74
+ nextCoord = new Pair(
75
+ startCoord.getX() + charIndex * direction.getX(),
76
+ startCoord.getY() + charIndex * direction.getY());
77
+
78
+ if (nextCoord.getY() < 1 ||
79
+ nextCoord.getY() > nRows ||
80
+ nextCoord.getX() < 1 ||
81
+ nextCoord.getX() > nCols) {
82
+
83
+ return Optional.empty();
84
+ }
85
+
86
+ if (grid[nextCoord.getY() - 1][nextCoord.getX() - 1] != word.charAt(charIndex)) {
87
+ return Optional.empty();
88
+ }
89
+ }
90
+
91
+ return Optional.of(new WordLocation(startCoord, nextCoord));
92
+ }
93
+
94
+ }
@@ -0,0 +1,37 @@
1
+ class Pair {
2
+
3
+ private final int x;
4
+
5
+ private final int y;
6
+
7
+ Pair(final int x, final int y) {
8
+ this.y = y;
9
+ this.x = x;
10
+ }
11
+
12
+ int getX() {
13
+ return x;
14
+ }
15
+
16
+ int getY() {
17
+ return y;
18
+ }
19
+
20
+ @Override
21
+ public boolean equals(final Object o) {
22
+ if (this == o) return true;
23
+ if (o == null || getClass() != o.getClass()) return false;
24
+
25
+ Pair pair = (Pair) o;
26
+
27
+ return x == pair.x && y == pair.y;
28
+ }
29
+
30
+ @Override
31
+ public int hashCode() {
32
+ int result = x;
33
+ result = 31 * result + y;
34
+ return result;
35
+ }
36
+
37
+ }
@@ -0,0 +1,29 @@
1
+ class WordLocation {
2
+
3
+ private final Pair startCoord;
4
+
5
+ private final Pair endCoord;
6
+
7
+ WordLocation(final Pair startCoord, final Pair endCoord) {
8
+ this.startCoord = startCoord;
9
+ this.endCoord = endCoord;
10
+ }
11
+
12
+ @Override
13
+ public boolean equals(final Object o) {
14
+ if (this == o) return true;
15
+ if (o == null || getClass() != o.getClass()) return false;
16
+
17
+ WordLocation that = (WordLocation) o;
18
+
19
+ return startCoord.equals(that.startCoord) && endCoord.equals(that.endCoord);
20
+ }
21
+
22
+ @Override
23
+ public int hashCode() {
24
+ int result = startCoord.hashCode();
25
+ result = 31 * result + endCoord.hashCode();
26
+ return result;
27
+ }
28
+
29
+ }
@@ -0,0 +1,278 @@
1
+ import org.junit.Before;
2
+ import org.junit.Test;
3
+
4
+ import java.util.*;
5
+
6
+ import static org.junit.Assert.assertEquals;
7
+
8
+ /*
9
+ * version: 1.0.0
10
+ */
11
+ public class WordSearcherTest {
12
+
13
+ private WordSearcher wordSearcher;
14
+
15
+ @Before
16
+ public void setUp() {
17
+ wordSearcher = new WordSearcher();
18
+ }
19
+
20
+ @Test
21
+ public void testLocatesWordsWrittenLeftToRight() {
22
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
23
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair(1, 10), new Pair(7, 10))));
24
+
25
+ Set<String> searchWords = expectedLocations.keySet();
26
+
27
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
28
+ searchWords,
29
+ new char[][]{
30
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
31
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
32
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
33
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
34
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
35
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
36
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
37
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
38
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
39
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}
40
+ }
41
+ );
42
+
43
+ assertEquals(expectedLocations, actualLocations);
44
+ }
45
+
46
+ @Test
47
+ public void testLocatesWordsWrittenRightToLeft() {
48
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
49
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair(1, 10), new Pair(7, 10))));
50
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair(6, 5), new Pair(1, 5))));
51
+
52
+ Set<String> searchWords = expectedLocations.keySet();
53
+
54
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
55
+ searchWords,
56
+ new char[][]{
57
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
58
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
59
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
60
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
61
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
62
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
63
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
64
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
65
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
66
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}
67
+ }
68
+ );
69
+
70
+ assertEquals(expectedLocations, actualLocations);
71
+ }
72
+
73
+ @Test
74
+ public void testLocatesWordsWrittenTopToBottom() {
75
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
76
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
77
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
78
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
79
+
80
+ Set<String> searchWords = expectedLocations.keySet();
81
+
82
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
83
+ searchWords,
84
+ new char[][]{
85
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
86
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
87
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
88
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
89
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
90
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
91
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
92
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
93
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
94
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}
95
+ }
96
+ );
97
+
98
+ assertEquals(expectedLocations, actualLocations);
99
+ }
100
+
101
+ @Test
102
+ public void testLocatesWordsWrittenBottomToTop() {
103
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
104
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
105
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
106
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
107
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
108
+
109
+ Set<String> searchWords = expectedLocations.keySet();
110
+
111
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
112
+ searchWords,
113
+ new char[][]{
114
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
115
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
116
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
117
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
118
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
119
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
120
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
121
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
122
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
123
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
124
+
125
+ assertEquals(expectedLocations, actualLocations);
126
+ }
127
+
128
+ @Test
129
+ public void testLocatesWordsWrittenTopLeftToBottomRight() {
130
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
131
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
132
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
133
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
134
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
135
+ expectedLocations.put("java", Optional.of(new WordLocation(new Pair( 1, 1), new Pair( 4, 4))));
136
+
137
+ Set<String> searchWords = expectedLocations.keySet();
138
+
139
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
140
+ searchWords,
141
+ new char[][]{
142
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
143
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
144
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
145
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
146
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
147
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
148
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
149
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
150
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
151
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
152
+
153
+ assertEquals(expectedLocations, actualLocations);
154
+ }
155
+
156
+ @Test
157
+ public void testLocatesWordsWrittenBottomRightToTopLeft() {
158
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
159
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
160
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
161
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
162
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
163
+ expectedLocations.put("java", Optional.of(new WordLocation(new Pair( 1, 1), new Pair( 4, 4))));
164
+ expectedLocations.put("lua", Optional.of(new WordLocation(new Pair( 8, 9), new Pair( 6, 7))));
165
+
166
+ Set<String> searchWords = expectedLocations.keySet();
167
+
168
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
169
+ searchWords,
170
+ new char[][]{
171
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
172
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
173
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
174
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
175
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
176
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
177
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
178
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
179
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
180
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
181
+
182
+ assertEquals(expectedLocations, actualLocations);
183
+ }
184
+
185
+ @Test
186
+ public void testLocatesWordsWrittenBottomLeftToTopRight() {
187
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
188
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
189
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
190
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
191
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
192
+ expectedLocations.put("java", Optional.of(new WordLocation(new Pair( 1, 1), new Pair( 4, 4))));
193
+ expectedLocations.put("lua", Optional.of(new WordLocation(new Pair( 8, 9), new Pair( 6, 7))));
194
+ expectedLocations.put("lisp", Optional.of(new WordLocation(new Pair( 3, 6), new Pair( 6, 3))));
195
+
196
+ Set<String> searchWords = expectedLocations.keySet();
197
+
198
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
199
+ searchWords,
200
+ new char[][]{
201
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
202
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
203
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
204
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
205
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
206
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
207
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
208
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
209
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
210
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
211
+
212
+ assertEquals(expectedLocations, actualLocations);
213
+ }
214
+
215
+ @Test
216
+ public void testLocatesWordsWrittenTopRightToBottomLeft() {
217
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
218
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
219
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
220
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
221
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
222
+ expectedLocations.put("java", Optional.of(new WordLocation(new Pair( 1, 1), new Pair( 4, 4))));
223
+ expectedLocations.put("lua", Optional.of(new WordLocation(new Pair( 8, 9), new Pair( 6, 7))));
224
+ expectedLocations.put("lisp", Optional.of(new WordLocation(new Pair( 3, 6), new Pair( 6, 3))));
225
+ expectedLocations.put("ruby", Optional.of(new WordLocation(new Pair( 8, 6), new Pair( 5, 9))));
226
+
227
+ Set<String> searchWords = expectedLocations.keySet();
228
+
229
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
230
+ searchWords,
231
+ new char[][]{
232
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
233
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
234
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
235
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
236
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
237
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
238
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
239
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
240
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
241
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
242
+
243
+ assertEquals(expectedLocations, actualLocations);
244
+ }
245
+
246
+ @Test
247
+ public void testFailsToLocateAWordsThatIsNotInThePuzzle() {
248
+ Map<String, Optional<WordLocation>> expectedLocations = new HashMap<>();
249
+ expectedLocations.put("clojure", Optional.of(new WordLocation(new Pair( 1, 10), new Pair( 7, 10))));
250
+ expectedLocations.put("elixir", Optional.of(new WordLocation(new Pair( 6, 5), new Pair( 1, 5))));
251
+ expectedLocations.put("ecmascript", Optional.of(new WordLocation(new Pair(10, 1), new Pair(10, 10))));
252
+ expectedLocations.put("rust", Optional.of(new WordLocation(new Pair( 9, 5), new Pair( 9, 2))));
253
+ expectedLocations.put("java", Optional.of(new WordLocation(new Pair( 1, 1), new Pair( 4, 4))));
254
+ expectedLocations.put("lua", Optional.of(new WordLocation(new Pair( 8, 9), new Pair( 6, 7))));
255
+ expectedLocations.put("lisp", Optional.of(new WordLocation(new Pair( 3, 6), new Pair( 6, 3))));
256
+ expectedLocations.put("ruby", Optional.of(new WordLocation(new Pair( 8, 6), new Pair( 5, 9))));
257
+ expectedLocations.put("haskell", Optional.empty());
258
+
259
+ Set<String> searchWords = expectedLocations.keySet();
260
+
261
+ Map<String, Optional<WordLocation>> actualLocations = wordSearcher.search(
262
+ searchWords,
263
+ new char[][]{
264
+ {'j', 'e', 'f', 'b', 'l', 'p', 'e', 'p', 'r', 'e'},
265
+ {'c', 'a', 'm', 'd', 'c', 'i', 'm', 'g', 't', 'c'},
266
+ {'o', 'i', 'v', 'o', 'k', 'p', 'r', 'j', 's', 'm'},
267
+ {'p', 'b', 'w', 'a', 's', 'q', 'r', 'o', 'u', 'a'},
268
+ {'r', 'i', 'x', 'i', 'l', 'e', 'l', 'h', 'r', 's'},
269
+ {'w', 'o', 'l', 'c', 'q', 'l', 'i', 'r', 'p', 'c'},
270
+ {'s', 'c', 'r', 'e', 'e', 'a', 'u', 'm', 'g', 'r'},
271
+ {'a', 'l', 'x', 'h', 'p', 'b', 'u', 'r', 'y', 'i'},
272
+ {'j', 'a', 'l', 'a', 'y', 'c', 'a', 'l', 'm', 'p'},
273
+ {'c', 'l', 'o', 'j', 'u', 'r', 'e', 'r', 'm', 't'}});
274
+
275
+ assertEquals(expectedLocations, actualLocations);
276
+ }
277
+
278
+ }
@@ -17,18 +17,15 @@ suite generators are named in the form `ProblemNameTestGenerator.scala`. Where
17
17
 
18
18
  [the shared problem metadata](https://github.com/exercism/x-common).
19
19
 
20
- For example, take a look at the `all-your-base/canonical-data.json` file in the x-common repository, as well
20
+ For example, take a look at the `bob/canonical-data.json` file in the x-common repository, as well
21
21
  as the following files in the xscala repository:
22
22
 
23
- 1. `testgen/src/main/scala/AllYourBaseTestGenerator.scala` - test suite generator for all-your-base
24
- 1. `exercises/all-your-base/src/test/scala/AllYourBase.scala`- generated test suite
23
+ 1. `testgen/src/main/scala/BobTestGenerator.scala` - test suite generator for bob
24
+ 1. `exercises/bob/src/test/scala/BobTest.scala`- generated test suite
25
25
 
26
- Since a generator was used, the`exercises/all-your-base/src/test/scala/AllYourBase.scala` will never be edited directly.
26
+ Since a generator was used, the`exercises/bob/src/test/scala/BobTest.scala` will never be edited directly.
27
27
  If there's a missing test case, then additional inputs/outputs should be submitted to the x-common repository.
28
28
 
29
- Note that the the test suite generators do not format the test suite source code. The generated test suite should be
30
- formatted before being submitted.
31
-
32
29
  When submitting new exercises we encourage that a test suite generator and generated test suite is
33
30
  included.
34
31
 
@@ -95,16 +95,7 @@
95
95
  "Logic",
96
96
  "Transforming"
97
97
  ]
98
- },
99
- {
100
- "slug":"perfect-numbers",
101
- "difficulty":2,
102
- "topics":[
103
- "Enumerations",
104
- "Integers",
105
- "Mathematics"
106
- ]
107
- },
98
+ },
108
99
  {
109
100
  "slug":"hamming",
110
101
  "difficulty":3,
@@ -185,6 +176,16 @@
185
176
  "Mathematics"
186
177
  ]
187
178
  },
179
+ {
180
+ "slug":"perfect-numbers",
181
+ "difficulty":3,
182
+ "topics":[
183
+ "Discriminated unions",
184
+ "Enumerations",
185
+ "Integers",
186
+ "Mathematics"
187
+ ]
188
+ },
188
189
  {
189
190
  "slug":"binary-search",
190
191
  "difficulty":3,
@@ -1,5 +1,6 @@
1
1
  import org.scalatest._
2
2
 
3
+ /** @version 1.0.0 */
3
4
  class BeerSongTest extends FunSuite {
4
5
 
5
6
  test("first generic verse") {
@@ -49,4 +50,4 @@ class BeerSongTest extends FunSuite {
49
50
  assert(expected == BeerSong.verses(99, 0))
50
51
  }
51
52
 
52
- }
53
+ }
@@ -1,17 +1,21 @@
1
1
  import NumberType.NumberType
2
2
 
3
3
  object PerfectNumbers {
4
- def classify(n: Int): NumberType = {
5
- val sumOfFactors
6
- = (1 until n)
7
- .foldLeft(0)((acc, i) => if (n % i == 0) acc + i else acc)
4
+ def classify(n: Int): Either[String, NumberType] = {
5
+ if (n <= 0)
6
+ Left("Classification is only possible for natural numbers.")
7
+ else {
8
+ val sumOfFactors
9
+ = (1 until n)
10
+ .foldLeft(0)((acc, i) => if (n % i == 0) acc + i else acc)
8
11
 
9
- if (sumOfFactors < n)
10
- NumberType.Deficient
11
- else if (sumOfFactors > n)
12
- NumberType.Abundant
13
- else
14
- NumberType.Perfect
12
+ if (sumOfFactors < n)
13
+ Right(NumberType.Deficient)
14
+ else if (sumOfFactors > n)
15
+ Right(NumberType.Abundant)
16
+ else
17
+ Right(NumberType.Perfect)
18
+ }
15
19
  }
16
20
  }
17
21
 
@@ -1,62 +1,69 @@
1
- import org.scalatest.{FlatSpec, Matchers}
1
+ import org.scalatest.{Matchers, FunSuite}
2
2
 
3
- class PerfectNumbersTest extends FlatSpec with Matchers {
4
- it should "handle deficient - 3" in {
5
- PerfectNumbers.classify(3) should be (NumberType.Deficient)
3
+ /** @version 1.0.1 */
4
+ class PerfectNumbersTest extends FunSuite with Matchers {
5
+
6
+ test("Smallest perfect number is classified correctly") {
7
+ PerfectNumbers.classify(6) should be (Right(NumberType.Perfect))
8
+ }
9
+
10
+ test("Medium perfect number is classified correctly") {
11
+ pending
12
+ PerfectNumbers.classify(28) should be (Right(NumberType.Perfect))
6
13
  }
7
14
 
8
- it should "handle deficient - 7" in {
15
+ test("Large perfect number is classified correctly") {
9
16
  pending
10
- PerfectNumbers.classify(7) should be (NumberType.Deficient)
17
+ PerfectNumbers.classify(33550336) should be (Right(NumberType.Perfect))
11
18
  }
12
19
 
13
- it should "handle deficient - 13" in {
20
+ test("Smallest abundant number is classified correctly") {
14
21
  pending
15
- PerfectNumbers.classify(13) should be (NumberType.Deficient)
22
+ PerfectNumbers.classify(12) should be (Right(NumberType.Abundant))
16
23
  }
17
24
 
18
- it should "handle deficient - 33550337" in {
25
+ test("Medium abundant number is classified correctly") {
19
26
  pending
20
- PerfectNumbers.classify(33550337) should be (NumberType.Deficient)
27
+ PerfectNumbers.classify(30) should be (Right(NumberType.Abundant))
21
28
  }
22
29
 
23
- it should "handle perfect - 6" in {
30
+ test("Large abundant number is classified correctly") {
24
31
  pending
25
- PerfectNumbers.classify(6) should be (NumberType.Perfect)
32
+ PerfectNumbers.classify(33550335) should be (Right(NumberType.Abundant))
26
33
  }
27
34
 
28
- it should "handle perfect - 28" in {
35
+ test("Smallest prime deficient number is classified correctly") {
29
36
  pending
30
- PerfectNumbers.classify(28) should be (NumberType.Perfect)
37
+ PerfectNumbers.classify(2) should be (Right(NumberType.Deficient))
31
38
  }
32
39
 
33
- it should "handle perfect - 33550336" in {
40
+ test("Smallest non-prime deficient number is classified correctly") {
34
41
  pending
35
- PerfectNumbers.classify(33550336) should be (NumberType.Perfect)
42
+ PerfectNumbers.classify(4) should be (Right(NumberType.Deficient))
36
43
  }
37
44
 
38
- it should "handle perfect - 496" in {
45
+ test("Medium deficient number is classified correctly") {
39
46
  pending
40
- PerfectNumbers.classify(496) should be (NumberType.Perfect)
47
+ PerfectNumbers.classify(32) should be (Right(NumberType.Deficient))
41
48
  }
42
49
 
43
- it should "handle abundant - 12" in {
50
+ test("Large deficient number is classified correctly") {
44
51
  pending
45
- PerfectNumbers.classify(12) should be (NumberType.Abundant)
52
+ PerfectNumbers.classify(33550337) should be (Right(NumberType.Deficient))
46
53
  }
47
54
 
48
- it should "handle abundant - 18" in {
55
+ test("Edge case (no factors other than itself) is classified correctly") {
49
56
  pending
50
- PerfectNumbers.classify(18) should be (NumberType.Abundant)
57
+ PerfectNumbers.classify(1) should be (Right(NumberType.Deficient))
51
58
  }
52
59
 
53
- it should "handle abundant - 20" in {
60
+ test("Zero is rejected (not a natural number)") {
54
61
  pending
55
- PerfectNumbers.classify(20) should be (NumberType.Abundant)
62
+ PerfectNumbers.classify(0) should be (Left("Classification is only possible for natural numbers."))
56
63
  }
57
64
 
58
- it should "handle abundant - 33550335" in {
65
+ test("Negative integer is rejected (not a natural number)") {
59
66
  pending
60
- PerfectNumbers.classify(33550335) should be (NumberType.Abundant)
67
+ PerfectNumbers.classify(-1) should be (Left("Classification is only possible for natural numbers."))
61
68
  }
62
- }
69
+ }
@@ -11,5 +11,7 @@ object BeerSongTestGenerator {
11
11
  println(s"-------------")
12
12
  println(code)
13
13
  println(s"-------------")
14
+
15
+ TestSuiteBuilder.writeToFile(code, new File("BeerSongTest.scala"))
14
16
  }
15
17
  }
@@ -0,0 +1,42 @@
1
+ import java.io.File
2
+
3
+ import testgen.TestSuiteBuilder.{toString, _}
4
+ import testgen._
5
+
6
+ object PerfectNumbersTestGenerator {
7
+ def main(args: Array[String]): Unit = {
8
+ val file = new File("src/main/resources/perfect-numbers.json")
9
+
10
+ def toEnumStr(str: String): String = {
11
+ str match {
12
+ case "perfect" => "NumberType.Perfect"
13
+ case "abundant" => "NumberType.Abundant"
14
+ case "deficient" => "NumberType.Deficient"
15
+ case _ => throw new IllegalStateException("Invalid NumberType -" + str)
16
+ }
17
+ }
18
+
19
+ def toString(expected: CanonicalDataParser.Expected): String = {
20
+ expected match {
21
+ case Left(error) => s"Left(${TestSuiteBuilder.toString(error)})"
22
+ case Right(exp) => s"Right(${toEnumStr(exp.toString)})"
23
+ }
24
+ }
25
+
26
+ def fromLabeledTest(argNames: String*): ToTestCaseData =
27
+ withLabeledTest { sut =>
28
+ labeledTest =>
29
+ val args = sutArgs(labeledTest.result, argNames: _*)
30
+ val property = labeledTest.property
31
+ val sutCall =
32
+ s"""PerfectNumbers.$property($args)"""
33
+ val expected = toString(labeledTest.expected)
34
+ TestCaseData(labeledTest.description, sutCall, expected)
35
+ }
36
+
37
+ val code = TestSuiteBuilder.build(file, fromLabeledTest("input"))
38
+ println(s"-------------")
39
+ println(code)
40
+ println(s"-------------")
41
+ }
42
+ }
@@ -66,7 +66,7 @@ object Exercise {
66
66
  private def flattenCases(cases: Cases): Cases =
67
67
  cases match {
68
68
  case Seq() => Seq()
69
- case (ltg: LabeledTestGroup) +: xs => ltg.cases ++ flattenCases(xs)
69
+ case (ltg: LabeledTestGroup) +: xs => flattenCases(ltg.cases) ++ flattenCases(xs)
70
70
  case (lt: LabeledTest) +: xs => lt +: flattenCases(xs)
71
71
  }
72
72
  }
@@ -86,7 +86,7 @@ object TestSuiteBuilder {
86
86
  private def toString(expected: CanonicalDataParser.Expected): String =
87
87
  expected.fold(error => s"Left(${toString(error)})", toString)
88
88
 
89
- private def toString(any: Any): String = {
89
+ def toString(any: Any): String = {
90
90
  def quote(str: String): String =
91
91
  if ("\"\n" exists (str.contains(_:Char))) "\"\"\"" else "\""
92
92
 
@@ -98,7 +98,7 @@ object TestSuiteBuilder {
98
98
  }
99
99
  }
100
100
 
101
- def toFile(text: String, dest: File): Unit = {
101
+ def writeToFile(text: String, dest: File): Unit = {
102
102
  val fileWriter = new FileWriter(dest)
103
103
  try { fileWriter.write(text) } finally fileWriter.close
104
104
  }
@@ -73,8 +73,7 @@
73
73
  {
74
74
  "slug": "allergies",
75
75
  "difficulty": 1,
76
- "topics": [
77
- ]
76
+ "topics": []
78
77
  },
79
78
  {
80
79
  "slug": "scrabble-score",
@@ -85,6 +84,11 @@
85
84
  "slug": "triangle",
86
85
  "difficulty": 1,
87
86
  "topics": []
87
+ },
88
+ {
89
+ "slug": "etl",
90
+ "difficulty": 1,
91
+ "topics": []
88
92
  }
89
93
  ],
90
94
  "deprecated": [],
@@ -0,0 +1,27 @@
1
+ "
2
+ " Version: 1.0.0
3
+ "
4
+
5
+ Before:
6
+ unlet! input expected
7
+
8
+ Execute (a single letter):
9
+ let input = {'1': ['A']}
10
+ let expected = {'a': 1}
11
+ AssertEqual expected, Transform(input)
12
+
13
+ Execute (single score with multiple letters):
14
+ let input = {'1': ['A', 'E', 'I', 'O', 'U']}
15
+ let expected = {'a': 1, 'e': 1, 'i': 1, 'u': 1, 'o': 1}
16
+ AssertEqual expected, Transform(input)
17
+
18
+ Execute (multiple scores with multiple letters):
19
+ let input = {'1': ['A', 'E'], '2': ['D', 'G']}
20
+ let expected = {'a': 1, 'd': 2, 'e': 1, 'g': 2}
21
+ AssertEqual expected, Transform(input)
22
+
23
+ Execute (multiple scores with differing numbers of letters):
24
+ let input = {'1': ['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T'], '2': ['D', 'G'], '3': ['B', 'C', 'M', 'P'], '4': ['F', 'H', 'V', 'W', 'Y'], '5': ['K'], '8': ['J', 'X'], '10': ['Q', 'Z']}
25
+ let expected = {'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10}
26
+ AssertEqual expected, Transform(input)
27
+
@@ -0,0 +1,12 @@
1
+ "
2
+ " We are going to do the Transform step of an Extract-Transform-Load.
3
+ "
4
+ " Example:
5
+ "
6
+ " :echo Transform({'1': ['a', 'b'], '2': ['c']})
7
+ " {'a': 1, 'b': 1, 'c': 2}
8
+ "
9
+
10
+ function! Transform(scores) abort
11
+ " your code goes here
12
+ endfunction
@@ -0,0 +1,11 @@
1
+ function! Transform(scores) abort
2
+ let data = {}
3
+
4
+ for [score, letters] in items(a:scores)
5
+ for letter in letters
6
+ let data[tolower(letter)] = str2nr(score)
7
+ endfor
8
+ endfor
9
+
10
+ return data
11
+ endfunction
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trackler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0.28
4
+ version: 2.1.0.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katrina Owen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-05 00:00:00.000000000 Z
11
+ date: 2017-06-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -4743,6 +4743,13 @@ files:
4743
4743
  - tracks/java/exercises/word-count/src/example/java/WordCount.java
4744
4744
  - tracks/java/exercises/word-count/src/main/java/WordCount.java
4745
4745
  - tracks/java/exercises/word-count/src/test/java/WordCountTest.java
4746
+ - tracks/java/exercises/word-search/build.gradle
4747
+ - tracks/java/exercises/word-search/src/example/java/Pair.java
4748
+ - tracks/java/exercises/word-search/src/example/java/WordLocation.java
4749
+ - tracks/java/exercises/word-search/src/example/java/WordSearcher.java
4750
+ - tracks/java/exercises/word-search/src/main/java/Pair.java
4751
+ - tracks/java/exercises/word-search/src/main/java/WordLocation.java
4752
+ - tracks/java/exercises/word-search/src/test/java/WordSearcherTest.java
4746
4753
  - tracks/java/exercises/wordy/build.gradle
4747
4754
  - tracks/java/exercises/wordy/src/example/java/WordProblemSolver.java
4748
4755
  - tracks/java/exercises/wordy/src/main/java/WordProblemSolver.java
@@ -8321,7 +8328,6 @@ files:
8321
8328
  - tracks/scala/exercises/pascals-triangle/src/test/scala/PascalsTriangleTest.scala
8322
8329
  - tracks/scala/exercises/perfect-numbers/build.sbt
8323
8330
  - tracks/scala/exercises/perfect-numbers/example.scala
8324
- - tracks/scala/exercises/perfect-numbers/src/main/scala/PerfectNumbers.scala
8325
8331
  - tracks/scala/exercises/perfect-numbers/src/test/scala/PerfectNumbersTest.scala
8326
8332
  - tracks/scala/exercises/phone-number/HINTS.md
8327
8333
  - tracks/scala/exercises/phone-number/build.sbt
@@ -8484,6 +8490,7 @@ files:
8484
8490
  - tracks/scala/testgen/src/main/scala/LeapTestGenerator.scala
8485
8491
  - tracks/scala/testgen/src/main/scala/NucleotideCountTestGenerator.scala
8486
8492
  - tracks/scala/testgen/src/main/scala/PangramsTestGenerator.scala
8493
+ - tracks/scala/testgen/src/main/scala/PerfectNumbersTestGenerator.scala
8487
8494
  - tracks/scala/testgen/src/main/scala/RailFenceCipherTestGenerator.scala
8488
8495
  - tracks/scala/testgen/src/main/scala/RaindropsTestGenerator.scala
8489
8496
  - tracks/scala/testgen/src/main/scala/SumOfMultiplesTestGenerator.scala
@@ -9275,6 +9282,9 @@ files:
9275
9282
  - tracks/vimscript/exercises/difference-of-squares/difference_of_squares.vader
9276
9283
  - tracks/vimscript/exercises/difference-of-squares/difference_of_squares.vim
9277
9284
  - tracks/vimscript/exercises/difference-of-squares/example.vim
9285
+ - tracks/vimscript/exercises/etl/etl.vader
9286
+ - tracks/vimscript/exercises/etl/etl.vim
9287
+ - tracks/vimscript/exercises/etl/example.vim
9278
9288
  - tracks/vimscript/exercises/hamming/example.vim
9279
9289
  - tracks/vimscript/exercises/hamming/hamming.vader
9280
9290
  - tracks/vimscript/exercises/hamming/hamming.vim