trackler 2.0.8.51 → 2.0.8.52
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.
- 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
|
+
|