trackler 2.1.0.28 → 2.1.0.29

Sign up to get free protection for your applications and to get access to all the features.
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