trackler 2.0.8.51 → 2.0.8.52
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/tracks/cpp/.editorconfig +21 -0
- data/tracks/cpp/exercises/anagram/anagram_test.cpp +2 -1
- data/tracks/cpp/exercises/nucleotide-count/example.cpp +5 -1
- data/tracks/cpp/exercises/nucleotide-count/nucleotide_count_test.cpp +5 -0
- data/tracks/cpp/exercises/queen-attack/queen_attack_test.cpp +8 -5
- data/tracks/elixir/config.json +8 -0
- data/tracks/elixir/exercises/hello-world/hello_world.exs +1 -1
- data/tracks/elixir/exercises/scale-generator/example.exs +119 -0
- data/tracks/elixir/exercises/scale-generator/scale_generator.exs +83 -0
- data/tracks/elixir/exercises/scale-generator/scale_generator_test.exs +282 -0
- data/tracks/go/README.md +17 -4
- data/tracks/go/gen/gen.go +85 -15
- data/tracks/java/exercises/pascals-triangle/src/example/java/PascalsTriangleGenerator.java +21 -0
- data/tracks/java/exercises/pascals-triangle/src/test/java/PascalsTriangleGeneratorTest.java +86 -0
- data/tracks/javascript/.github/stale.yml +0 -0
- data/tracks/javascript/exercises/leap/HINT.md +52 -0
- data/tracks/javascript/exercises/leap/leap.js +6 -2
- data/tracks/objective-c/docs/TESTS.md +5 -4
- data/tracks/purescript/config.json +8 -0
- data/tracks/purescript/exercises/crypto-square/bower.json +18 -0
- data/tracks/purescript/exercises/crypto-square/examples/src/CryptoSquare.purs +63 -0
- data/tracks/purescript/exercises/crypto-square/src/CryptoSquare.purs +6 -0
- data/tracks/purescript/exercises/crypto-square/test/Main.purs +92 -0
- data/tracks/ruby/exercises/hello-world/.meta/.version +1 -1
- data/tracks/ruby/exercises/hello-world/example.tt +7 -5
- data/tracks/ruby/exercises/hello-world/hello_world_test.rb +4 -15
- data/tracks/ruby/lib/hello_world_cases.rb +5 -5
- metadata +14 -6
- data/tracks/clojurescript/.github/ISSUE_TEMPLATE.md +0 -9
- data/tracks/cpp/exercises/queen-attack/require_equal_containers.h +0 -88
- data/tracks/java/exercises/pascals-triangle/src/example/java/PascalsTriangle.java +0 -26
- data/tracks/java/exercises/pascals-triangle/src/test/java/PascalsTriangleTest.java +0 -85
data/tracks/go/README.md
CHANGED
@@ -181,10 +181,23 @@ directory within each exercise that makes use of a test cases generator. This
|
|
181
181
|
*.meta* directory will be ignored when a user fetches an exercise.
|
182
182
|
|
183
183
|
Whenever the shared JSON data changes, the test cases will need to be regenerated.
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
184
|
+
The generator will first look for a local copy of the **x-common** repository.
|
185
|
+
If there isn't one it will attempt to get the relevant json data for the
|
186
|
+
exercise from the **x-common** repository on GitHub.
|
187
|
+
|
188
|
+
To use a local copy of the **x-common** repository, make sure that it has been
|
189
|
+
cloned into the same parent-directory as the **xgo** repository.
|
190
|
+
|
191
|
+
```sh
|
192
|
+
$ tree -L 1 .
|
193
|
+
.
|
194
|
+
├── x-common
|
195
|
+
└── xgo
|
196
|
+
```
|
197
|
+
|
198
|
+
To regenerate the test cases, navigate into the **xgo** directory and run
|
199
|
+
`go run exercises/<exercise>/.meta/gen.go`. You should see that the
|
200
|
+
`<exercise>/cases_test.go` file has changed. Commit the change.
|
188
201
|
|
189
202
|
## Pull requests
|
190
203
|
|
data/tracks/go/gen/gen.go
CHANGED
@@ -7,18 +7,20 @@ import (
|
|
7
7
|
"fmt"
|
8
8
|
"go/format"
|
9
9
|
"io/ioutil"
|
10
|
+
"log"
|
11
|
+
"net/http"
|
10
12
|
"os"
|
11
13
|
"os/exec"
|
12
14
|
"path/filepath"
|
13
15
|
"runtime"
|
14
16
|
"text/template"
|
17
|
+
"time"
|
15
18
|
)
|
16
19
|
|
17
|
-
// dirMetadata is the location of the x-common repository
|
18
|
-
//
|
19
|
-
//
|
20
|
-
//
|
21
|
-
// repository. E.g.
|
20
|
+
// dirMetadata is the location of the x-common repository on the filesystem.
|
21
|
+
// We're making the assumption that the x-common repository has been cloned to
|
22
|
+
// the same parent directory as the xgo repository.
|
23
|
+
// E.g.
|
22
24
|
//
|
23
25
|
// $ tree -L 1 .
|
24
26
|
// .
|
@@ -31,7 +33,21 @@ var dirMetadata string
|
|
31
33
|
// the exercise directory. Falls back to the present working directory.
|
32
34
|
var dirExercise string
|
33
35
|
|
34
|
-
//
|
36
|
+
// genClient creates an http client with a 10 second timeout so we don't get
|
37
|
+
// stuck waiting for a response.
|
38
|
+
var genClient = &http.Client{Timeout: 10 * time.Second}
|
39
|
+
|
40
|
+
const (
|
41
|
+
// canonicalDataURL is the URL for the raw canonical-data.json data,
|
42
|
+
// requires exercise name.
|
43
|
+
canonicalDataURL = "https://raw.githubusercontent.com/exercism/x-common/master/exercises/%s/canonical-data.json"
|
44
|
+
// commitsURL is the GitHub api endpoint for the canonical-data.json
|
45
|
+
// file commit history, requires exercise name.
|
46
|
+
commitsURL = "https://api.github.com/repos/exercism/x-common/commits?path=exercises/%s/canonical-data.json"
|
47
|
+
)
|
48
|
+
|
49
|
+
// Header tells how the test data was generated, for display in the header of
|
50
|
+
// cases_test.go
|
35
51
|
type Header struct {
|
36
52
|
// Ori is a deprecated short name for Origin.
|
37
53
|
// TODO: Remove Ori once everything switches to Origin.
|
@@ -70,11 +86,21 @@ func Gen(exercise string, j interface{}, t *template.Template) error {
|
|
70
86
|
return errors.New("unable to determine current path")
|
71
87
|
}
|
72
88
|
jFile := filepath.Join("exercises", exercise, "canonical-data.json")
|
73
|
-
// find and read the json source file
|
74
|
-
|
89
|
+
// try to find and read the local json source file
|
90
|
+
log.Printf("[LOCAL] fetching %s test data\n", exercise)
|
91
|
+
jPath, jOrigin, jCommit := getLocal(jFile)
|
92
|
+
if jPath != "" {
|
93
|
+
log.Printf("[LOCAL] source: %s\n", jPath)
|
94
|
+
}
|
75
95
|
jSrc, err := ioutil.ReadFile(filepath.Join(jPath, jFile))
|
76
96
|
if err != nil {
|
77
|
-
|
97
|
+
// fetch json data remotely if there's no local file
|
98
|
+
log.Println("[LOCAL] No test data found")
|
99
|
+
log.Printf("[REMOTE] fetching %s test data\n", exercise)
|
100
|
+
jSrc, jOrigin, jCommit, err = getRemote(exercise)
|
101
|
+
if err != nil {
|
102
|
+
return err
|
103
|
+
}
|
78
104
|
}
|
79
105
|
|
80
106
|
// unmarshal the json source to a Go structure
|
@@ -91,7 +117,7 @@ func Gen(exercise string, j interface{}, t *template.Template) error {
|
|
91
117
|
Version string
|
92
118
|
}
|
93
119
|
if err := json.Unmarshal(jSrc, &commonMetadata); err != nil {
|
94
|
-
return fmt.Errorf(`
|
120
|
+
return fmt.Errorf(`didn't contain version: %v`, err)
|
95
121
|
}
|
96
122
|
|
97
123
|
// package up a little meta data
|
@@ -107,7 +133,7 @@ func Gen(exercise string, j interface{}, t *template.Template) error {
|
|
107
133
|
|
108
134
|
// render the Go test cases
|
109
135
|
var b bytes.Buffer
|
110
|
-
if err
|
136
|
+
if err := t.Execute(&b, &d); err != nil {
|
111
137
|
return err
|
112
138
|
}
|
113
139
|
// clean it up
|
@@ -119,17 +145,17 @@ func Gen(exercise string, j interface{}, t *template.Template) error {
|
|
119
145
|
return ioutil.WriteFile(filepath.Join(dirExercise, "cases_test.go"), src, 0666)
|
120
146
|
}
|
121
147
|
|
122
|
-
func
|
148
|
+
func getLocal(jFile string) (jPath, jOrigin, jCommit string) {
|
123
149
|
// Ideally draw from a .json which is pulled from the official x-common
|
124
150
|
// repository. For development however, accept a file in current directory
|
125
151
|
// if there is no .json in source control. Also allow an override in any
|
126
152
|
// case by environment variable.
|
127
|
-
if jPath
|
153
|
+
if jPath := os.Getenv("EXTEST"); jPath > "" {
|
128
154
|
return jPath, "local file", "" // override
|
129
155
|
}
|
130
156
|
c := exec.Command("git", "log", "-1", "--oneline", jFile)
|
131
157
|
c.Dir = dirMetadata
|
132
|
-
|
158
|
+
origin, err := c.Output()
|
133
159
|
if err != nil {
|
134
160
|
return "", "local file", "" // no source control
|
135
161
|
}
|
@@ -137,5 +163,49 @@ func getPath(jFile string) (jPath, jOrigin, jCommit string) {
|
|
137
163
|
return "", "local file", "" // not in source control
|
138
164
|
}
|
139
165
|
// good. return source control dir and commit.
|
140
|
-
return c.Dir, "exercism/x-common", string(bytes.TrimSpace(
|
166
|
+
return c.Dir, "exercism/x-common", string(bytes.TrimSpace(origin))
|
167
|
+
}
|
168
|
+
|
169
|
+
func getRemote(exercise string) (body []byte, jOrigin string, jCommit string, err error) {
|
170
|
+
url := fmt.Sprintf(canonicalDataURL, exercise)
|
171
|
+
resp, err := genClient.Get(url)
|
172
|
+
if err != nil {
|
173
|
+
return []byte{}, "", "", err
|
174
|
+
}
|
175
|
+
if resp.StatusCode != http.StatusOK {
|
176
|
+
return []byte{}, "", "", fmt.Errorf("error fetching remote data: %s", resp.Status)
|
177
|
+
}
|
178
|
+
defer resp.Body.Close()
|
179
|
+
body, err = ioutil.ReadAll(resp.Body)
|
180
|
+
if err != nil {
|
181
|
+
return []byte{}, "", "", err
|
182
|
+
}
|
183
|
+
c, err := getRemoteCommit(exercise)
|
184
|
+
if err != nil {
|
185
|
+
// we always expect to have the commit in the cases_test.go
|
186
|
+
// file, so return the error if we can't fetch it
|
187
|
+
return []byte{}, "", "", err
|
188
|
+
}
|
189
|
+
log.Printf("[REMOTE] source: %s\n", url)
|
190
|
+
return body, "exercism/x-common", c, nil
|
191
|
+
}
|
192
|
+
|
193
|
+
func getRemoteCommit(exercise string) (string, error) {
|
194
|
+
type Commits struct {
|
195
|
+
Sha string
|
196
|
+
Commit struct {
|
197
|
+
Message string
|
198
|
+
}
|
199
|
+
}
|
200
|
+
resp, err := genClient.Get(fmt.Sprintf(commitsURL, exercise))
|
201
|
+
if err != nil {
|
202
|
+
return "", err
|
203
|
+
}
|
204
|
+
defer resp.Body.Close()
|
205
|
+
var c []Commits
|
206
|
+
err = json.NewDecoder(resp.Body).Decode(&c)
|
207
|
+
if err != nil {
|
208
|
+
return "", err
|
209
|
+
}
|
210
|
+
return fmt.Sprintf("%s %s", c[0].Sha[0:7], c[0].Commit.Message), nil
|
141
211
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class PascalsTriangleGenerator {
|
2
|
+
|
3
|
+
int[][] generateTriangle(int rows) {
|
4
|
+
if (rows < 0) {
|
5
|
+
throw new IllegalArgumentException("Rows can't be negative!");
|
6
|
+
}
|
7
|
+
|
8
|
+
int[][] triangle = new int[rows][];
|
9
|
+
|
10
|
+
for (int i = 0; i < rows; i++) {
|
11
|
+
triangle[i] = new int[i + 1];
|
12
|
+
triangle[i][0] = 1;
|
13
|
+
for (int j = 1; j < i; j++) {
|
14
|
+
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
|
15
|
+
}
|
16
|
+
triangle[i][i] = 1;
|
17
|
+
}
|
18
|
+
return triangle;
|
19
|
+
}
|
20
|
+
|
21
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import org.junit.Before;
|
2
|
+
import org.junit.Test;
|
3
|
+
import org.junit.Ignore;
|
4
|
+
import org.junit.Rule;
|
5
|
+
import org.junit.rules.ExpectedException;
|
6
|
+
|
7
|
+
|
8
|
+
import static org.junit.Assert.assertArrayEquals;
|
9
|
+
import static org.junit.Assert.assertEquals;
|
10
|
+
|
11
|
+
/*
|
12
|
+
* version: 1.0.0
|
13
|
+
*/
|
14
|
+
public class PascalsTriangleGeneratorTest {
|
15
|
+
|
16
|
+
private PascalsTriangleGenerator pascalsTriangleGenerator;
|
17
|
+
|
18
|
+
@Before
|
19
|
+
public void setUp() {
|
20
|
+
pascalsTriangleGenerator = new PascalsTriangleGenerator();
|
21
|
+
}
|
22
|
+
|
23
|
+
@Rule
|
24
|
+
public ExpectedException thrown = ExpectedException.none();
|
25
|
+
|
26
|
+
@Test
|
27
|
+
public void testTriangleWithZeroRows() {
|
28
|
+
int[][] expectedOutput = new int[][]{};
|
29
|
+
|
30
|
+
assertArrayEquals(expectedOutput, pascalsTriangleGenerator.generateTriangle(0));
|
31
|
+
}
|
32
|
+
|
33
|
+
@Ignore
|
34
|
+
@Test
|
35
|
+
public void testTriangleWithOneRow() {
|
36
|
+
int[][] expectedOutput = new int[][]{
|
37
|
+
{1}
|
38
|
+
};
|
39
|
+
|
40
|
+
assertArrayEquals(expectedOutput, pascalsTriangleGenerator.generateTriangle(1));
|
41
|
+
}
|
42
|
+
|
43
|
+
@Ignore
|
44
|
+
@Test
|
45
|
+
public void testTriangleWithTwoRows() {
|
46
|
+
int[][] expectedOutput = new int[][]{
|
47
|
+
{1},
|
48
|
+
{1, 1}
|
49
|
+
};
|
50
|
+
|
51
|
+
assertArrayEquals(expectedOutput, pascalsTriangleGenerator.generateTriangle(2));
|
52
|
+
}
|
53
|
+
|
54
|
+
@Ignore
|
55
|
+
@Test
|
56
|
+
public void testTriangleWithThreeRows() {
|
57
|
+
int[][] expectedOutput = new int[][]{
|
58
|
+
{1},
|
59
|
+
{1, 1},
|
60
|
+
{1, 2, 1}
|
61
|
+
};
|
62
|
+
|
63
|
+
assertArrayEquals(expectedOutput, pascalsTriangleGenerator.generateTriangle(3));
|
64
|
+
}
|
65
|
+
|
66
|
+
@Ignore
|
67
|
+
@Test
|
68
|
+
public void testTriangleWithFourRows() {
|
69
|
+
int[][] expectedOutput = new int[][]{
|
70
|
+
{1},
|
71
|
+
{1, 1},
|
72
|
+
{1, 2, 1},
|
73
|
+
{1, 3, 3, 1}
|
74
|
+
};
|
75
|
+
|
76
|
+
assertArrayEquals(expectedOutput, pascalsTriangleGenerator.generateTriangle(4));
|
77
|
+
}
|
78
|
+
|
79
|
+
@Ignore
|
80
|
+
@Test
|
81
|
+
public void testValidatesNotNegativeRows() {
|
82
|
+
thrown.expect(IllegalArgumentException.class);
|
83
|
+
pascalsTriangleGenerator.generateTriangle(-1);
|
84
|
+
}
|
85
|
+
|
86
|
+
}
|
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
This is the first test for this exercise:
|
2
|
+
|
3
|
+
```javascript
|
4
|
+
it('is not very common', function() {
|
5
|
+
var year = new Year(2015);
|
6
|
+
expect(year.isLeap()).toBe(false);
|
7
|
+
});
|
8
|
+
```
|
9
|
+
|
10
|
+
Each test in the exercise follows the same pattern:
|
11
|
+
|
12
|
+
1. A new Year is instantiated with a value and stored in a variable: year.
|
13
|
+
2. The test calls an instance method, isLeap(), from the variable `year`.
|
14
|
+
|
15
|
+
The `Year` function is a constructor, which means that it is specifically designed to provide a template for new objects.
|
16
|
+
|
17
|
+
When a new `Year` object is created:
|
18
|
+
|
19
|
+
```javascript
|
20
|
+
var year = new Year(2015);
|
21
|
+
```
|
22
|
+
|
23
|
+
We expect `year` to, at the very least, have a specific value (in this case 2015) assigned to it. Otherwise it would not represent an actual year.
|
24
|
+
|
25
|
+
This means that we must store the value passed as a parameter when the new `Year` is created (2015), so that the new `Year` can access it.
|
26
|
+
|
27
|
+
The way a constructor stores these fundamental values (instance variables) is like this:
|
28
|
+
|
29
|
+
```javascript
|
30
|
+
var Constructor = function(input) {
|
31
|
+
this.value = input;
|
32
|
+
};
|
33
|
+
```
|
34
|
+
|
35
|
+
The `this` in the constructor refers to the newly created instance of the `Constructor`.
|
36
|
+
|
37
|
+
Once the input (parameter) is stored in an instance variable, any instance methods, such as:
|
38
|
+
|
39
|
+
```javascript
|
40
|
+
Constructor.prototype.instanceMethod = function() {
|
41
|
+
// this method can now access the input by calling `this.value`
|
42
|
+
};
|
43
|
+
```
|
44
|
+
|
45
|
+
The instance method accesses the input using `this.value` (in this example).
|
46
|
+
|
47
|
+
This is why code needs to be written in two separate functions:
|
48
|
+
|
49
|
+
1. The first function, `Year`, is a constructor that serves as a template for year objects that are created with their value (such as 2015).
|
50
|
+
This function needs to store the input when a new `Year` is created.
|
51
|
+
2. The second function, isLeap(), is an instance method that is called from a new `year`.
|
52
|
+
This function (method) should contain the logic to determine if the given year is a leap year.
|
@@ -3,9 +3,13 @@
|
|
3
3
|
// convenience to get you started writing code faster.
|
4
4
|
//
|
5
5
|
|
6
|
-
var Year = function() {
|
6
|
+
var Year = function(input) {
|
7
|
+
//
|
8
|
+
// YOUR CODE GOES HERE
|
9
|
+
//
|
10
|
+
};
|
7
11
|
|
8
|
-
Year.prototype.isLeap = function(
|
12
|
+
Year.prototype.isLeap = function() {
|
9
13
|
//
|
10
14
|
// YOUR CODE GOES HERE
|
11
15
|
//
|
@@ -29,19 +29,20 @@ __Note:__ If you receive the error "No visible `@interface` for ExerciseName dec
|
|
29
29
|
|
30
30
|
### A Test Runner
|
31
31
|
|
32
|
-
An alternative to manually generating the project file is to use a test runner utility
|
32
|
+
An alternative to manually generating the project file is to use a test runner utility written in ruby, [`objc`](https://rubygems.org/gems/objc/), that will create a project file for you with the test file, header file and source file.
|
33
33
|
|
34
34
|
```bash
|
35
35
|
$ gem install objc
|
36
|
-
$ brew install xctool
|
37
36
|
```
|
38
37
|
|
39
38
|
Run the tests with:
|
40
39
|
|
41
40
|
```bash
|
42
|
-
$ objc ExerciseName
|
41
|
+
$ objc -x ExerciseName
|
43
42
|
```
|
44
43
|
|
45
|
-
|
44
|
+
(Note the `-x`/`--xcodebuild` flag, which specifies using `xcodebuild` instead of `xctool`. The latter does not work with Xcode's latest releases.)
|
45
|
+
|
46
|
+
The objc utility uses the exercise name to find the test file, `ExerciseNameTest.m`, the header file, `ExerciseName.h` and source file `ExerciseName.m`. The files are inserted into a temporary Xcode Project and then `xcodebuild` is used to run the tests for the project.
|
46
47
|
|
47
48
|
While `objc` makes it so you never have to launch Xcode to complete these exercises, the error messages and feedback through the command-line are not as clear as through the Xcode user interface.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
{
|
2
|
+
"name": "crypto-square",
|
3
|
+
"ignore": [
|
4
|
+
"**/.*",
|
5
|
+
"node_modules",
|
6
|
+
"bower_components",
|
7
|
+
"output"
|
8
|
+
],
|
9
|
+
"dependencies": {
|
10
|
+
"purescript-prelude": "^3.0.0",
|
11
|
+
"purescript-strings": "^3.0.0",
|
12
|
+
"purescript-unicode": "^3.0.1"
|
13
|
+
},
|
14
|
+
"devDependencies": {
|
15
|
+
"purescript-psci-support": "^3.0.0",
|
16
|
+
"purescript-test-unit": "^11.0.0"
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CryptoSquare
|
2
|
+
( normalizedPlaintext
|
3
|
+
, plaintextSegments
|
4
|
+
, encoded
|
5
|
+
, ciphertext
|
6
|
+
) where
|
7
|
+
|
8
|
+
import Prelude
|
9
|
+
import Data.Array as A
|
10
|
+
import Data.Array (filter, fromFoldable, replicate, toUnfoldable, (:))
|
11
|
+
import Data.Char.Unicode (isAlphaNum)
|
12
|
+
import Data.Foldable (maximum)
|
13
|
+
import Data.Int (ceil, toNumber)
|
14
|
+
import Data.List (transpose)
|
15
|
+
import Data.Maybe (fromMaybe)
|
16
|
+
import Data.String (drop, fromCharArray, joinWith, length, take, toCharArray, toLower)
|
17
|
+
import Math (sqrt)
|
18
|
+
|
19
|
+
normalizedPlaintext :: String -> String
|
20
|
+
normalizedPlaintext
|
21
|
+
= toCharArray
|
22
|
+
>>> filter isAlphaNum
|
23
|
+
>>> fromCharArray
|
24
|
+
>>> toLower
|
25
|
+
|
26
|
+
plaintextSegments :: String -> Array String
|
27
|
+
plaintextSegments str = toSquare norm
|
28
|
+
where norm = normalizedPlaintext str
|
29
|
+
cols = sqrt (length norm # toNumber) # ceil
|
30
|
+
toSquare "" = []
|
31
|
+
toSquare s = take cols s : toSquare (drop cols s)
|
32
|
+
|
33
|
+
transposeArray :: forall a. Array (Array a) -> Array (Array a)
|
34
|
+
transposeArray
|
35
|
+
= toUnfoldable
|
36
|
+
>>> map toUnfoldable
|
37
|
+
>>> transpose
|
38
|
+
>>> map fromFoldable
|
39
|
+
>>> fromFoldable
|
40
|
+
|
41
|
+
encoded :: String -> String
|
42
|
+
encoded = plaintextSegments
|
43
|
+
>>> map toCharArray
|
44
|
+
>>> transposeArray
|
45
|
+
>>> map fromCharArray
|
46
|
+
>>> joinWith ""
|
47
|
+
|
48
|
+
spaces :: Int -> Array Char
|
49
|
+
spaces n = replicate n ' '
|
50
|
+
|
51
|
+
equalPad :: Array (Array Char) -> Array (Array Char)
|
52
|
+
equalPad arr = map pad arr
|
53
|
+
where width = fromMaybe 0 (maximum $ map A.length arr)
|
54
|
+
pad el = el <> spaces (width - A.length el)
|
55
|
+
|
56
|
+
ciphertext :: String -> String
|
57
|
+
ciphertext = plaintextSegments
|
58
|
+
>>> map toCharArray
|
59
|
+
>>> transposeArray
|
60
|
+
>>> equalPad
|
61
|
+
>>> map fromCharArray
|
62
|
+
>>> joinWith " "
|
63
|
+
|