Skip to content

Commit cfba1d9

Browse files
committedDec 20, 2020
Add Rail Fence Cipher.
1 parent 71db2d2 commit cfba1d9

File tree

6 files changed

+254
-202
lines changed

6 files changed

+254
-202
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ a set of rules that precisely define a sequence of operations.
143143
* `A` [Travelling Salesman Problem](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city
144144
* **Cryptography**
145145
* `B` [Polynomial Hash](src/algorithms/cryptography/polynomial-hash) - rolling hash function based on polynomial
146-
* `B` [Rail Fence Cypher](src/algorithms/cryptography/rail-fence-cipher) - a transposition cipher algorithm for encoding messages
146+
* `B` [Rail Fence Cipher](src/algorithms/cryptography/rail-fence-cipher) - a transposition cipher algorithm for encoding messages
147147
* `B` [Caesar Cipher](src/algorithms/cryptography/caesar-cipher) - simple substitution cipher
148148
* `B` [Hill Cipher](src/algorithms/cryptography/hill-cipher) - substitution cipher based on linear algebra
149149
* **Machine Learning**
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
# Rail fence Cipher
1+
# Rail Fence Cipher
22

3-
This is a [transposition cipher](https://en.wikipedia.org/wiki/Transposition_cipher) in which the message is split accross a set of rails on a fence for encoding. The fence is populated with the message's characters, starting at the top left and adding a character on each position, traversing them diagonally to the bottom. Upon reaching the last rail, the direction should then turn diagonal and upwards up to the very first rail in a zig-zag motion. Rinse and repeat until the message is fully disposed across the fence. The encoded message is the result of concatenating the text in each rail, from top to bottom.
3+
The **rail fence cipher** (also called a **zigzag cipher**) is a [transposition cipher](https://en.wikipedia.org/wiki/Transposition_cipher) in which the message is split across a set of rails on a fence for encoding. The fence is populated with the message's characters, starting at the top left and adding a character on each position, traversing them diagonally to the bottom. Upon reaching the last rail, the direction should then turn diagonal and upwards up to the very first rail in a zig-zag motion. Rinse and repeat until the message is fully disposed across the fence. The encoded message is the result of concatenating the text in each rail, from top to bottom.
44

5-
From [wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher), this is what the message `WE ARE DISCOVERED. FLEE AT ONCE` looks like on a 3-rail fence:
5+
From [wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher), this is what the message `WE ARE DISCOVERED. FLEE AT ONCE` looks like on a `3`-rail fence:
66

77
```
88
W . . . E . . . C . . . R . . . L . . . T . . . E
99
. E . R . D . S . O . E . E . F . E . A . O . C .
1010
. . A . . . I . . . V . . . D . . . E . . . N . .
1111
-------------------------------------------------
12-
ECRLTEERDSOEEFEAOCAIVDEN
12+
WECRLTEERDSOEEFEAOCAIVDEN
1313
```
1414

15-
The message can then be decoded by re-creating the encode fence, with the same traversal pattern, except characters should only be added on one rail at a time. To ilustrate that, a dash can be added on the rails that are not supposed to be poupated yet. This is what the fence would look like after populating the first rail, the dashes represent positions that were visited but not populated.
15+
The message can then be decoded by re-creating the encoded fence, with the same traversal pattern, except characters should only be added on one rail at a time. To illustrate that, a dash can be added on the rails that are not supposed to be populated yet. This is what the fence would look like after populating the first rail, the dashes represent positions that were visited but not populated.
1616

1717
```
1818
W . . . E . . . C . . . R . . . L . . . T . . . E
@@ -22,4 +22,7 @@ W . . . E . . . C . . . R . . . L . . . T . . . E
2222

2323
It's time to start populating the next rail once the number of visited fence positions is equal to the number of characters in the message.
2424

25-
[Learn more](https://crypto.interactive-maths.com/rail-fence-cipher.html)
25+
## References
26+
27+
- [Rail Fence Cipher on Wikipedia](https://en.wikipedia.org/wiki/Rail_fence_cipher)
28+
- [Rail Fence Cipher Calculator](https://crypto.interactive-maths.com/rail-fence-cipher.html)
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,43 @@
1-
import encodeRailFenceCipher from '../encodeRailFence';
2-
import decodeRailFenceCipher from '../decodeRailFence';
1+
import { encodeRailFenceCipher, decodeRailFenceCipher } from '../railFenceCipher';
32

4-
describe('rail fence cipher', () => {
3+
describe('railFenceCipher', () => {
54
it('encodes a string correctly for base=3', () => {
65
expect(encodeRailFenceCipher('', 3)).toBe('');
7-
expect(encodeRailFenceCipher('WEAREDISCOVEREDFLEEATONCE', 3)).toBe('WECRLTEERDSOEEFEAOCAIVDEN');
8-
expect(encodeRailFenceCipher('Hello, World!', 3)).toBe('Hoo!el,Wrdl l');
6+
expect(encodeRailFenceCipher('12345', 3)).toBe(
7+
'15243',
8+
);
9+
expect(encodeRailFenceCipher('WEAREDISCOVEREDFLEEATONCE', 3)).toBe(
10+
'WECRLTEERDSOEEFEAOCAIVDEN',
11+
);
12+
expect(encodeRailFenceCipher('Hello, World!', 3)).toBe(
13+
'Hoo!el,Wrdl l',
14+
);
915
});
1016

1117
it('decodes a string correctly for base=3', () => {
1218
expect(decodeRailFenceCipher('', 3)).toBe('');
13-
expect(decodeRailFenceCipher('WECRLTEERDSOEEFEAOCAIVDEN', 3)).toBe('WEAREDISCOVEREDFLEEATONCE');
14-
expect(decodeRailFenceCipher('Hoo!el,Wrdl l', 3)).toBe('Hello, World!');
19+
expect(decodeRailFenceCipher('WECRLTEERDSOEEFEAOCAIVDEN', 3)).toBe(
20+
'WEAREDISCOVEREDFLEEATONCE',
21+
);
22+
expect(decodeRailFenceCipher('Hoo!el,Wrdl l', 3)).toBe(
23+
'Hello, World!',
24+
);
25+
expect(decodeRailFenceCipher('15243', 3)).toBe(
26+
'12345',
27+
);
1528
});
1629

1730
it('encodes a string correctly for base=4', () => {
1831
expect(encodeRailFenceCipher('', 4)).toBe('');
19-
expect(encodeRailFenceCipher('THEYAREATTACKINGFROMTHENORTH', 4)).toBe('TEKOOHRACIRMNREATANFTETYTGHH');
32+
expect(encodeRailFenceCipher('THEYAREATTACKINGFROMTHENORTH', 4)).toBe(
33+
'TEKOOHRACIRMNREATANFTETYTGHH',
34+
);
2035
});
2136

2237
it('decodes a string correctly for base=4', () => {
2338
expect(decodeRailFenceCipher('', 4)).toBe('');
24-
expect(decodeRailFenceCipher('TEKOOHRACIRMNREATANFTETYTGHH', 4)).toBe('THEYAREATTACKINGFROMTHENORTH');
39+
expect(decodeRailFenceCipher('TEKOOHRACIRMNREATANFTETYTGHH', 4)).toBe(
40+
'THEYAREATTACKINGFROMTHENORTH',
41+
);
2542
});
2643
});

‎src/algorithms/cryptography/rail-fence-cipher/decodeRailFence.js

-108
This file was deleted.

‎src/algorithms/cryptography/rail-fence-cipher/encodeRailFence.js

-52
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,242 @@
1+
/**
2+
* @typedef {string[]} Rail
3+
* @typedef {Rail[]} Fence
4+
* @typedef {number} Direction
5+
*/
6+
17
/**
28
* @constant DIRECTIONS
39
* @type {object}
4-
* @property {number} UP
5-
* @property {number} DOWN
10+
* @property {Direction} UP
11+
* @property {Direction} DOWN
612
*/
7-
export const DIRECTIONS = { UP: -1, DOWN: 1 };
13+
const DIRECTIONS = { UP: -1, DOWN: 1 };
814

915
/**
10-
* @param {number} rows
16+
* Builds a fence with a specific number of rows.
1117
*
12-
* @returns {Array}
18+
* @param {number} rowsNum
19+
* @returns {Fence}
1320
*/
14-
export const buildFence = (rows) => Array(rows)
15-
.fill()
21+
const buildFence = (rowsNum) => Array(rowsNum)
22+
.fill(null)
1623
.map(() => []);
1724

1825
/**
19-
* @param {object} params
20-
* @param {number} params.railCount
21-
* @param {number} params.currentRail
22-
* @param {number} params.direction
26+
* Get next direction to move (based on the current one) while traversing the fence.
2327
*
24-
* @returns {number}
28+
* @param {object} params
29+
* @param {number} params.railCount - Number of rows in the fence
30+
* @param {number} params.currentRail - Current row that we're visiting
31+
* @param {Direction} params.direction - Current direction
32+
* @returns {Direction} - The next direction to take
2533
*/
26-
export const getNextDirection = ({ railCount, currentRail, direction }) => {
34+
const getNextDirection = ({ railCount, currentRail, direction }) => {
2735
switch (currentRail) {
28-
case 0: return DIRECTIONS.DOWN;
29-
case railCount - 1: return DIRECTIONS.UP;
30-
default: return direction;
36+
case 0:
37+
// Go down if we're on top of the fence.
38+
return DIRECTIONS.DOWN;
39+
case railCount - 1:
40+
// Go up if we're at the bottom of the fence.
41+
return DIRECTIONS.UP;
42+
default:
43+
// Continue with the same direction if we're in the middle of the fence.
44+
return direction;
3145
}
3246
};
3347

3448
/**
35-
* Given a rail, adds a char to it
36-
* if it matches a targetIndex.
37-
* @callback charAdder
38-
* @param {number} rail
39-
* @param {currentRail} number
49+
* @param {number} targetRailIndex
50+
* @param {string} letter
51+
* @returns {Function}
4052
*/
53+
const addCharToRail = (targetRailIndex, letter) => {
54+
/**
55+
* Given a rail, adds a char to it if it matches a targetIndex.
56+
*
57+
* @param {Rail} rail
58+
* @param {number} currentRail
59+
* @returns {Rail}
60+
*/
61+
function onEachRail(rail, currentRail) {
62+
return currentRail === targetRailIndex
63+
? [...rail, letter]
64+
: rail;
65+
}
66+
return onEachRail;
67+
};
4168

4269
/**
43-
* @param {number} targetIndex
44-
* @param {string} letter
70+
* Hangs the characters on the fence.
4571
*
46-
* @returns {charAdder}
72+
* @param {object} params
73+
* @param {Fence} params.fence
74+
* @param {number} params.currentRail
75+
* @param {Direction} params.direction
76+
* @param {string[]} params.chars
77+
* @returns {Fence}
78+
*/
79+
const fillEncodeFence = ({
80+
fence,
81+
currentRail,
82+
direction,
83+
chars,
84+
}) => {
85+
if (chars.length === 0) {
86+
// All chars have been placed on a fence.
87+
return fence;
88+
}
89+
90+
const railCount = fence.length;
91+
92+
// Getting the next character to place on a fence.
93+
const [letter, ...nextChars] = chars;
94+
const nextDirection = getNextDirection({
95+
railCount,
96+
currentRail,
97+
direction,
98+
});
99+
100+
return fillEncodeFence({
101+
fence: fence.map(addCharToRail(currentRail, letter)),
102+
currentRail: currentRail + nextDirection,
103+
direction: nextDirection,
104+
chars: nextChars,
105+
});
106+
};
107+
108+
/**
109+
* @param {object} params
110+
* @param {number} params.strLen
111+
* @param {string[]} params.chars
112+
* @param {Fence} params.fence
113+
* @param {number} params.targetRail
114+
* @param {Direction} params.direction
115+
* @param {number[]} params.coords
116+
* @returns {Fence}
47117
*/
48-
export const addChar = (targetIndex, letter) => (rail, currentRail) => {
49-
return (currentRail === targetIndex ? [...rail, letter] : rail);
118+
const fillDecodeFence = (params) => {
119+
const {
120+
strLen, chars, fence, targetRail, direction, coords,
121+
} = params;
122+
123+
const railCount = fence.length;
124+
125+
if (chars.length === 0) {
126+
return fence;
127+
}
128+
129+
const [currentRail, currentColumn] = coords;
130+
const shouldGoNextRail = currentColumn === strLen - 1;
131+
const nextDirection = shouldGoNextRail
132+
? DIRECTIONS.DOWN
133+
: getNextDirection(
134+
{ railCount, currentRail, direction },
135+
);
136+
const nextRail = shouldGoNextRail ? targetRail + 1 : targetRail;
137+
const nextCoords = [
138+
shouldGoNextRail ? 0 : currentRail + nextDirection,
139+
shouldGoNextRail ? 0 : currentColumn + 1,
140+
];
141+
142+
const shouldAddChar = currentRail === targetRail;
143+
const [currentChar, ...remainderChars] = chars;
144+
const nextString = shouldAddChar ? remainderChars : chars;
145+
const nextFence = shouldAddChar ? fence.map(addCharToRail(currentRail, currentChar)) : fence;
146+
147+
return fillDecodeFence({
148+
strLen,
149+
chars: nextString,
150+
fence: nextFence,
151+
targetRail: nextRail,
152+
direction: nextDirection,
153+
coords: nextCoords,
154+
});
155+
};
156+
157+
/**
158+
* @param {object} params
159+
* @param {number} params.strLen
160+
* @param {Fence} params.fence
161+
* @param {number} params.currentRail
162+
* @param {Direction} params.direction
163+
* @param {number[]} params.code
164+
* @returns {string}
165+
*/
166+
const decodeFence = (params) => {
167+
const {
168+
strLen,
169+
fence,
170+
currentRail,
171+
direction,
172+
code,
173+
} = params;
174+
175+
if (code.length === strLen) {
176+
return code.join('');
177+
}
178+
179+
const railCount = fence.length;
180+
181+
const [currentChar, ...nextRail] = fence[currentRail];
182+
const nextDirection = getNextDirection(
183+
{ railCount, currentRail, direction },
184+
);
185+
186+
return decodeFence({
187+
railCount,
188+
strLen,
189+
currentRail: currentRail + nextDirection,
190+
direction: nextDirection,
191+
code: [...code, currentChar],
192+
fence: fence.map((rail, idx) => (idx === currentRail ? nextRail : rail)),
193+
});
194+
};
195+
196+
/**
197+
* Encodes the message using Rail Fence Cipher.
198+
*
199+
* @param {string} string - The string to be encoded
200+
* @param {number} railCount - The number of rails in a fence
201+
* @returns {string} - Encoded string
202+
*/
203+
export const encodeRailFenceCipher = (string, railCount) => {
204+
const fence = buildFence(railCount);
205+
206+
const filledFence = fillEncodeFence({
207+
fence,
208+
currentRail: 0,
209+
direction: DIRECTIONS.DOWN,
210+
chars: string.split(''),
211+
});
212+
213+
return filledFence.flat().join('');
214+
};
215+
216+
/**
217+
* Decodes the message using Rail Fence Cipher.
218+
*
219+
* @param {string} string - Encoded string
220+
* @param {number} railCount - The number of rows in a fence
221+
* @returns {string} - Decoded string.
222+
*/
223+
export const decodeRailFenceCipher = (string, railCount) => {
224+
const strLen = string.length;
225+
const emptyFence = buildFence(railCount);
226+
const filledFence = fillDecodeFence({
227+
strLen,
228+
chars: string.split(''),
229+
fence: emptyFence,
230+
targetRail: 0,
231+
direction: DIRECTIONS.DOWN,
232+
coords: [0, 0],
233+
});
234+
235+
return decodeFence({
236+
strLen,
237+
fence: filledFence,
238+
currentRail: 0,
239+
direction: DIRECTIONS.DOWN,
240+
code: [],
241+
});
50242
};

0 commit comments

Comments
 (0)
Please sign in to comment.