Skip to content

Commit d84a5a5

Browse files
Merge branch 'master' into fix/mobius-function-integer-overflow
2 parents 36446d2 + 56e2699 commit d84a5a5

5 files changed

Lines changed: 206 additions & 31 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
<dependency>
113113
<groupId>com.puppycrawl.tools</groupId>
114114
<artifactId>checkstyle</artifactId>
115-
<version>13.9.0</version>
115+
<version>13.10.0</version>
116116
</dependency>
117117
</dependencies>
118118
</plugin>

src/main/java/com/thealgorithms/ciphers/RailFenceCipher.java

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.thealgorithms.ciphers;
22

3-
import java.util.Arrays;
4-
53
/**
64
* The rail fence cipher (also called a zigzag cipher) is a classical type of transposition cipher.
75
* It derives its name from the manner in which encryption is performed, in analogy to a fence built with horizontal rails.
@@ -14,28 +12,27 @@ public class RailFenceCipher {
1412
// Encrypts the input string using the rail fence cipher method with the given number of rails.
1513
public String encrypt(String str, int rails) {
1614

15+
checkInput(str, rails);
16+
1717
// Base case of single rail or rails are more than the number of characters in the string
1818
if (rails == 1 || rails >= str.length()) {
1919
return str;
2020
}
2121

22-
// Boolean flag to determine if the movement is downward or upward in the rail matrix.
22+
// Boolean flag to determine if the movement is downward or upward in the rail pattern.
2323
boolean down = true;
24-
// Create a 2D array to represent the rails (rows) and the length of the string (columns).
25-
char[][] strRail = new char[rails][str.length()];
26-
27-
// Initialize all positions in the rail matrix with a placeholder character ('\n').
24+
// Collect the characters of every rail separately. Using one buffer per rail (instead of a
25+
// rails x length matrix with a placeholder character) keeps every character of the input,
26+
// including characters that would otherwise be indistinguishable from the placeholder.
27+
StringBuilder[] railBuffers = new StringBuilder[rails];
2828
for (int i = 0; i < rails; i++) {
29-
Arrays.fill(strRail[i], '\n');
29+
railBuffers[i] = new StringBuilder();
3030
}
3131

32-
int row = 0; // Start at the first row
33-
int col = 0; // Start at the first column
32+
int row = 0; // Start at the first rail
3433

35-
int i = 0;
36-
37-
// Fill the rail matrix with characters from the string based on the rail pattern.
38-
while (col < str.length()) {
34+
// Distribute the characters of the string over the rails following the zigzag pattern.
35+
for (int i = 0; i < str.length(); i++) {
3936
// Change direction to down when at the first row.
4037
if (row == 0) {
4138
down = true;
@@ -45,33 +42,28 @@ else if (row == rails - 1) {
4542
down = false;
4643
}
4744

48-
// Place the character in the current position of the rail matrix.
49-
strRail[row][col] = str.charAt(i);
50-
col++; // Move to the next column.
45+
// Append the character to the rail it belongs to.
46+
railBuffers[row].append(str.charAt(i));
5147
// Move to the next row based on the direction.
5248
if (down) {
5349
row++;
5450
} else {
5551
row--;
5652
}
57-
58-
i++;
5953
}
6054

61-
// Construct the encrypted string by reading characters row by row.
62-
StringBuilder encryptedString = new StringBuilder();
63-
for (char[] chRow : strRail) {
64-
for (char ch : chRow) {
65-
if (ch != '\n') {
66-
encryptedString.append(ch);
67-
}
68-
}
55+
// Construct the encrypted string by reading the rails top to bottom.
56+
StringBuilder encryptedString = new StringBuilder(str.length());
57+
for (StringBuilder railBuffer : railBuffers) {
58+
encryptedString.append(railBuffer);
6959
}
7060
return encryptedString.toString();
7161
}
7262
// Decrypts the input string using the rail fence cipher method with the given number of rails.
7363
public String decrypt(String str, int rails) {
7464

65+
checkInput(str, rails);
66+
7567
// Base case of single rail or rails are more than the number of characters in the string
7668
if (rails == 1 || rails >= str.length()) {
7769
return str;
@@ -144,4 +136,14 @@ else if (row == rails - 1) {
144136

145137
return decryptedString.toString();
146138
}
139+
140+
// Rejects inputs the zigzag pattern is not defined for.
141+
private static void checkInput(String str, int rails) {
142+
if (str == null) {
143+
throw new IllegalArgumentException("Input string must not be null");
144+
}
145+
if (rails <= 0) {
146+
throw new IllegalArgumentException("Number of rails must be positive, but was " + rails);
147+
}
148+
}
147149
}

src/main/java/com/thealgorithms/datastructures/trees/SegmentTree.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ public class SegmentTree {
88

99
/* Constructor which takes the size of the array and the array as a parameter*/
1010
public SegmentTree(int n, int[] arr) {
11+
if (arr == null) {
12+
throw new IllegalArgumentException("Input array must not be null");
13+
}
14+
if (n <= 0 || n > arr.length) {
15+
throw new IllegalArgumentException("Size must be in the range [1, " + arr.length + "], but was " + n);
16+
}
1117
this.n = n;
1218
int x = (int) (Math.ceil(Math.log(n) / Math.log(2)));
1319
int segSize = 2 * (int) Math.pow(2, x) - 1;
1420

1521
this.segTree = new int[segSize];
1622
this.arr = arr;
17-
this.n = n;
1823
constructTree(arr, 0, n - 1, 0);
1924
}
2025

@@ -47,7 +52,8 @@ private void updateTree(int start, int end, int index, int diff, int segIndex) {
4752

4853
/* A function to update the value at a particular index*/
4954
public void update(int index, int value) {
50-
if (index < 0 || index > n) {
55+
// Valid positions are 0..n-1; index == n is out of bounds and must not reach arr[index].
56+
if (index < 0 || index >= n) {
5157
return;
5258
}
5359

@@ -73,7 +79,8 @@ private int getSumTree(int start, int end, int qStart, int qEnd, int segIndex) {
7379

7480
/* A function to query the sum of the subarray [start...end]*/
7581
public int getSum(int start, int end) {
76-
if (start < 0 || end > n || start > end) {
82+
// The last queryable position is n-1, so end == n is an out of range query.
83+
if (start < 0 || end >= n || start > end) {
7784
return 0;
7885
}
7986
return getSumTree(0, n - 1, start, end, 0);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.thealgorithms.ciphers;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.CsvSource;
9+
import org.junit.jupiter.params.provider.ValueSource;
10+
11+
class RailFenceCipherTest {
12+
13+
private final RailFenceCipher railFenceCipher = new RailFenceCipher();
14+
15+
@Test
16+
void testEncrypt() {
17+
assertEquals("WECRLTEERDSOEEFEAOCAIVDEN", railFenceCipher.encrypt("WEAREDISCOVEREDFLEEATONCE", 3));
18+
}
19+
20+
@Test
21+
void testDecrypt() {
22+
assertEquals("WEAREDISCOVEREDFLEEATONCE", railFenceCipher.decrypt("WECRLTEERDSOEEFEAOCAIVDEN", 3));
23+
}
24+
25+
@ParameterizedTest
26+
@CsvSource({"HELLOWORLD, 2", "HELLOWORLD, 3", "HELLOWORLD, 4", "ATTACKATDAWN, 5", "abcdefghij, 6"})
27+
void testRoundTrip(String message, int rails) {
28+
assertEquals(message, railFenceCipher.decrypt(railFenceCipher.encrypt(message, rails), rails));
29+
}
30+
31+
/**
32+
* Every character of the input must survive encryption, including the ones that used to collide
33+
* with the placeholder that marked unused cells of the rail matrix.
34+
*/
35+
@ParameterizedTest
36+
@ValueSource(strings = {"ab\ncdef", "line1\nline2\nline3", "\n\n\n\n\n", "a\nb", "tabs\tand\nnewlines\r\n"})
37+
void testControlCharactersArePreserved(String message) {
38+
for (int rails = 2; rails <= 5; rails++) {
39+
String encrypted = railFenceCipher.encrypt(message, rails);
40+
assertEquals(message.length(), encrypted.length(), "characters were dropped with " + rails + " rails");
41+
assertEquals(message, railFenceCipher.decrypt(encrypted, rails), "round trip failed with " + rails + " rails");
42+
}
43+
}
44+
45+
@Test
46+
void testEncryptWithNewlineMatchesReferencePattern() {
47+
// Rails of "ab\ncdef" with 3 rails: {a, d} / {b, c, e} / {\n, f}
48+
assertEquals("adbce\nf", railFenceCipher.encrypt("ab\ncdef", 3));
49+
}
50+
51+
@ParameterizedTest
52+
@CsvSource({"HELLO, 1", "HELLO, 5", "HELLO, 9", "'', 1", "'', 4"})
53+
void testDegenerateRailCountsReturnInput(String message, int rails) {
54+
assertEquals(message, railFenceCipher.encrypt(message, rails));
55+
assertEquals(message, railFenceCipher.decrypt(message, rails));
56+
}
57+
58+
@ParameterizedTest
59+
@ValueSource(ints = {0, -1, -7})
60+
void testNonPositiveRailCountThrows(int rails) {
61+
assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt("HELLO", rails));
62+
assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt("HELLO", rails));
63+
}
64+
65+
@Test
66+
void testNullInputThrows() {
67+
assertThrows(IllegalArgumentException.class, () -> railFenceCipher.encrypt(null, 3));
68+
assertThrows(IllegalArgumentException.class, () -> railFenceCipher.decrypt(null, 3));
69+
}
70+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.thealgorithms.datastructures.trees;
2+
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.CsvSource;
10+
import org.junit.jupiter.params.provider.ValueSource;
11+
12+
class SegmentTreeTest {
13+
14+
private static SegmentTree treeOf(int... values) {
15+
return new SegmentTree(values.length, values);
16+
}
17+
18+
@ParameterizedTest
19+
@CsvSource({"0, 4, 15", "0, 0, 1", "4, 4, 5", "1, 3, 9", "2, 4, 12"})
20+
void testRangeSums(int start, int end, int expected) {
21+
assertEquals(expected, treeOf(1, 2, 3, 4, 5).getSum(start, end));
22+
}
23+
24+
@Test
25+
void testSingleElementTree() {
26+
SegmentTree tree = treeOf(42);
27+
assertEquals(42, tree.getSum(0, 0));
28+
tree.update(0, 7);
29+
assertEquals(7, tree.getSum(0, 0));
30+
}
31+
32+
@Test
33+
void testUpdateIsReflectedInSubsequentQueries() {
34+
SegmentTree tree = treeOf(1, 2, 3, 4, 5);
35+
tree.update(2, 10);
36+
assertEquals(22, tree.getSum(0, 4));
37+
assertEquals(16, tree.getSum(1, 3));
38+
tree.update(0, -1);
39+
assertEquals(20, tree.getSum(0, 4));
40+
}
41+
42+
@Test
43+
void testNegativeValues() {
44+
SegmentTree tree = treeOf(-5, 3, -2, 8);
45+
assertEquals(4, tree.getSum(0, 3));
46+
assertEquals(-4, tree.getSum(0, 2));
47+
}
48+
49+
/**
50+
* index == n is past the last element, so it must be rejected by the guard instead of reaching
51+
* the backing array and throwing {@link ArrayIndexOutOfBoundsException}.
52+
*/
53+
@ParameterizedTest
54+
@ValueSource(ints = {5, 6, 100, -1})
55+
void testUpdateOutOfRangeIndexIsIgnored(int index) {
56+
SegmentTree tree = treeOf(1, 2, 3, 4, 5);
57+
assertDoesNotThrow(() -> tree.update(index, 99));
58+
assertEquals(15, tree.getSum(0, 4), "out of range update must not modify the tree");
59+
}
60+
61+
@ParameterizedTest
62+
@CsvSource({"0, 5", "0, 6", "3, 2", "-1, 3", "5, 5"})
63+
void testOutOfRangeQueriesReturnZero(int start, int end) {
64+
assertEquals(0, treeOf(1, 2, 3, 4, 5).getSum(start, end));
65+
}
66+
67+
@Test
68+
void testConstructorRejectsInvalidSize() {
69+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(0, new int[] {1, 2, 3}));
70+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(-1, new int[] {1, 2, 3}));
71+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(4, new int[] {1, 2, 3}));
72+
}
73+
74+
@Test
75+
void testConstructorRejectsNullArray() {
76+
assertThrows(IllegalArgumentException.class, () -> new SegmentTree(3, null));
77+
}
78+
79+
@ParameterizedTest
80+
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17})
81+
void testMatchesBruteForceForVariousSizes(int size) {
82+
int[] values = new int[size];
83+
for (int i = 0; i < size; i++) {
84+
values[i] = i * 3 - 4;
85+
}
86+
SegmentTree tree = new SegmentTree(size, values.clone());
87+
88+
for (int start = 0; start < size; start++) {
89+
int expected = 0;
90+
for (int end = start; end < size; end++) {
91+
expected += values[end];
92+
assertEquals(expected, tree.getSum(start, end), "sum of [" + start + ", " + end + "] with size " + size);
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)