From a3c2e0330e5913b3601602ff377899a08f9602f9 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:39:23 +0300
Subject: [PATCH 01/18] Validate sprint8 CLI input
---
src/main/java/algorithms/sprint8/Crib.java | 32 ++++++++-
.../java/algorithms/sprint8/PackedPrefix.java | 65 ++++++++++++++++++-
2 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/src/main/java/algorithms/sprint8/Crib.java b/src/main/java/algorithms/sprint8/Crib.java
index 18d15d2..c8f665b 100644
--- a/src/main/java/algorithms/sprint8/Crib.java
+++ b/src/main/java/algorithms/sprint8/Crib.java
@@ -35,12 +35,22 @@
public class Crib {
static boolean solve(String text, String[] words) {
- int totalLength = 0;
+ if (!isLowercaseWord(text)) {
+ return false;
+ }
+
+ long totalLength = 0;
for (String word : words) {
+ if (!isLowercaseWord(word)) {
+ return false;
+ }
totalLength += word.length();
+ if (totalLength >= Integer.MAX_VALUE) {
+ return false;
+ }
}
- Trie trie = new Trie(totalLength + 1);
+ Trie trie = new Trie((int) totalLength + 1);
for (String word : words) {
trie.add(word);
}
@@ -76,6 +86,17 @@ static boolean solve(String text, String[] words) {
return dp[chars.length];
}
+ private static boolean isLowercaseWord(String word) {
+ for (int i = 0; i < word.length(); i++) {
+ char current = word.charAt(i);
+ if (current < 'a' || current > 'z') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
static final class Trie {
final int[][] next;
final boolean[] terminal;
@@ -207,6 +228,13 @@ private static void run() throws Exception {
String text = in.next();
int n = in.nextInt();
+ if (n < 0) {
+ out.writeString("NO");
+ out.writeByte('\n');
+ out.flush();
+ return;
+ }
+
String[] words = new String[n];
for (int i = 0; i < n; i++) {
diff --git a/src/main/java/algorithms/sprint8/PackedPrefix.java b/src/main/java/algorithms/sprint8/PackedPrefix.java
index 66c2420..859ab05 100644
--- a/src/main/java/algorithms/sprint8/PackedPrefix.java
+++ b/src/main/java/algorithms/sprint8/PackedPrefix.java
@@ -40,6 +40,10 @@ public class PackedPrefix {
private static final int MAX_UNPACKED_LENGTH = 100_000;
static String solve(String[] packedStrings) {
+ if (packedStrings.length == 0 || !isValidPacked(packedStrings[0])) {
+ return "";
+ }
+
String first = decodePrefix(packedStrings[0], MAX_UNPACKED_LENGTH);
int prefixLength = first.length();
@@ -47,6 +51,9 @@ static String solve(String[] packedStrings) {
if (prefixLength == 0) {
break;
}
+ if (!isValidPacked(packedStrings[i])) {
+ return "";
+ }
prefixLength = commonPrefixWithPacked(packedStrings[i], first, prefixLength);
}
@@ -234,6 +241,45 @@ private static int[] buildMatchingBrackets(String packed) {
return matchingBracket;
}
+ private static boolean isValidPacked(String packed) {
+ int top = 0;
+
+ for (int i = 0; i < packed.length(); i++) {
+ char current = packed.charAt(i);
+
+ if (current >= 'a' && current <= 'z') {
+ continue;
+ }
+
+ if (current >= '0' && current <= '9') {
+ if (i + 1 >= packed.length() || packed.charAt(i + 1) != '[') {
+ return false;
+ }
+ continue;
+ }
+
+ if (current == '[') {
+ if (i == 0 || packed.charAt(i - 1) < '0' || packed.charAt(i - 1) > '9') {
+ return false;
+ }
+ top++;
+ continue;
+ }
+
+ if (current == ']') {
+ if (top == 0) {
+ return false;
+ }
+ top--;
+ continue;
+ }
+
+ return false;
+ }
+
+ return top == 0;
+ }
+
// -------------------- FAST INPUT --------------------
static final class FastIn {
private final InputStream in;
@@ -357,7 +403,20 @@ private static void run() throws Exception {
FastOut out = new FastOut(System.out);
int n = in.nextInt();
- String first = decodePrefix(in.next(), MAX_UNPACKED_LENGTH);
+ if (n <= 0) {
+ out.writeByte('\n');
+ out.flush();
+ return;
+ }
+
+ String packedFirst = in.next();
+ if (!isValidPacked(packedFirst)) {
+ out.writeByte('\n');
+ out.flush();
+ return;
+ }
+
+ String first = decodePrefix(packedFirst, MAX_UNPACKED_LENGTH);
int prefixLength = first.length();
for (int i = 1; i < n; i++) {
@@ -366,6 +425,10 @@ private static void run() throws Exception {
}
String packed = in.next();
+ if (!isValidPacked(packed)) {
+ prefixLength = 0;
+ break;
+ }
prefixLength = commonPrefixWithPacked(packed, first, prefixLength);
}
From a98db0d620c4a52009d0069a85f5bcb074f12d95 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:43:36 +0300
Subject: [PATCH 02/18] Validate sprint7 solver input bounds
---
.../java/algorithms/sprint7/EqualSums.java | 27 ++++++++++++++++---
.../sprint7/LevenshteinDistance.java | 17 ++++++++----
2 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/src/main/java/algorithms/sprint7/EqualSums.java b/src/main/java/algorithms/sprint7/EqualSums.java
index 0ab867e..f6f82c3 100644
--- a/src/main/java/algorithms/sprint7/EqualSums.java
+++ b/src/main/java/algorithms/sprint7/EqualSums.java
@@ -31,18 +31,30 @@
Пространственная сложность — O(S / 64).
*/
public class EqualSums {
+ private static final int MAX_GAMES = 300;
+ private static final int MAX_TOTAL = MAX_GAMES * 300;
static boolean solve(int[] points) {
- int total = 0;
+ if (points.length > MAX_GAMES) {
+ throw new IllegalArgumentException("Too many games");
+ }
+
+ long total = 0;
for (int point : points) {
+ if (point < 0) {
+ throw new IllegalArgumentException("Point value is out of range");
+ }
total += point;
+ if (total > MAX_TOTAL) {
+ throw new IllegalArgumentException("Total points are out of range");
+ }
}
- if ((total & 1) == 1) {
+ if ((total & 1L) == 1L) {
return false;
}
- int target = total / 2;
+ int target = (int) (total / 2);
long[] reachable = new long[(target >> 6) + 1];
reachable[0] = 1L;
@@ -168,10 +180,17 @@ private static void run() throws Exception {
FastOut out = new FastOut(System.out);
int n = in.nextInt();
+ if (n < 0 || n > MAX_GAMES) {
+ throw new IOException("Number of games is out of range");
+ }
int[] points = new int[n];
for (int i = 0; i < n; i++) {
- points[i] = in.nextInt();
+ int point = in.nextInt();
+ if (point < 0) {
+ throw new IOException("Point value is out of range");
+ }
+ points[i] = point;
}
out.writeAscii(solve(points) ? "True" : "False");
diff --git a/src/main/java/algorithms/sprint7/LevenshteinDistance.java b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
index 482e1fb..2547124 100644
--- a/src/main/java/algorithms/sprint7/LevenshteinDistance.java
+++ b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
@@ -27,8 +27,12 @@
Пространственная сложность: O(min(n, m)), потому что хранится только две строки динамики.
*/
public class LevenshteinDistance {
+ private static final int MAX_LINE_LENGTH = 1000;
static int solve(String first, String second) {
+ if (first.length() > MAX_LINE_LENGTH || second.length() > MAX_LINE_LENGTH) {
+ throw new IllegalArgumentException("Input line is too long");
+ }
String s = first;
String t = second;
@@ -94,8 +98,8 @@ private int read() throws IOException {
return buf[ptr++];
}
- String nextLine() throws IOException {
- byte[] tmp = new byte[1024];
+ String nextLine(int maxLength) throws IOException {
+ byte[] tmp = new byte[Math.min(1024, maxLength + 1)];
int size = 0;
int c = read();
@@ -105,8 +109,11 @@ String nextLine() throws IOException {
while (c != -1 && c != '\n') {
if (c != '\r') {
+ if (size == maxLength) {
+ throw new IOException("Input line is too long");
+ }
if (size == tmp.length) {
- byte[] grown = new byte[tmp.length * 2];
+ byte[] grown = new byte[Math.min(tmp.length * 2, maxLength)];
System.arraycopy(tmp, 0, grown, 0, tmp.length);
tmp = grown;
}
@@ -169,8 +176,8 @@ private static void run() throws Exception {
FastIn in = new FastIn(System.in);
FastOut out = new FastOut(System.out);
- String s = in.nextLine();
- String t = in.nextLine();
+ String s = in.nextLine(MAX_LINE_LENGTH);
+ String t = in.nextLine(MAX_LINE_LENGTH);
out.writeInt(solve(s, t));
out.writeByte('\n');
From d46cea259e2aad12bf92514ff230c261032bf004 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:43:50 +0300
Subject: [PATCH 03/18] Harden WaterWorld input dimensions
---
.../java/algorithms/sprint6/WaterWorld.java | 39 ++++++++++++++++---
1 file changed, 34 insertions(+), 5 deletions(-)
diff --git a/src/main/java/algorithms/sprint6/WaterWorld.java b/src/main/java/algorithms/sprint6/WaterWorld.java
index e4198d2..ff368c1 100644
--- a/src/main/java/algorithms/sprint6/WaterWorld.java
+++ b/src/main/java/algorithms/sprint6/WaterWorld.java
@@ -32,8 +32,25 @@
public class WaterWorld {
+ private static final int MAX_CELLS = 10_000_000;
+
+ private static int checkedTotalCells(int n, int m) {
+ if (n <= 0 || m <= 0) {
+ throw new IllegalArgumentException("Dimensions must be positive");
+ }
+
+ long total = (long) n * m;
+ if (total > MAX_CELLS) {
+ throw new IllegalArgumentException("Map is too large");
+ }
+ return (int) total;
+ }
+
static int[] solve(byte[] map, int n, int m) {
- int total = n * m;
+ int total = checkedTotalCells(n, m);
+ if (map.length != total) {
+ throw new IllegalArgumentException("Map size does not match dimensions");
+ }
int[] queue = new int[total];
int islandCount = 0;
@@ -130,10 +147,17 @@ int nextInt() throws IOException {
int val = 0;
while (c > ' ') {
- val = val * 10 + c - '0';
+ if (c < '0' || c > '9') {
+ throw new IOException("Invalid integer token");
+ }
+ int digit = c - '0';
+ if (val > (Integer.MAX_VALUE - digit) / 10) {
+ throw new IOException("Integer token is too large");
+ }
+ val = val * 10 + digit;
c = read();
}
- return val * sign;
+ return sign * val;
}
byte[] nextBytes(int length) throws IOException {
@@ -209,7 +233,8 @@ private static void run() throws Exception {
int n = in.nextInt();
int m = in.nextInt();
- byte[] map = new byte[n * m];
+ int total = checkedTotalCells(n, m);
+ byte[] map = new byte[total];
for (int row = 0; row < n; row++) {
byte[] line = in.nextBytes(m);
@@ -288,7 +313,11 @@ public static void main(String[] args) throws Exception {
if (System.getProperty("os.name").startsWith("Windows")) {
test();
} else {
- run();
+ try {
+ run();
+ } catch (IllegalArgumentException | IOException ignored) {
+ // Invalid input is rejected before allocating arrays.
+ }
}
}
}
From d78d6a52181fa55da2849a591ae332f428d579b5 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:44:30 +0300
Subject: [PATCH 04/18] Validate DorogayaSet input bounds
---
.../java/algorithms/sprint6/DorogayaSet.java | 51 ++++++++++++++++---
.../java/algorithms/AlgorithmCliTest.java | 6 +++
2 files changed, 49 insertions(+), 8 deletions(-)
diff --git a/src/main/java/algorithms/sprint6/DorogayaSet.java b/src/main/java/algorithms/sprint6/DorogayaSet.java
index a1a7c20..d47b1fb 100644
--- a/src/main/java/algorithms/sprint6/DorogayaSet.java
+++ b/src/main/java/algorithms/sprint6/DorogayaSet.java
@@ -43,8 +43,14 @@
public class DorogayaSet {
static final String FAIL = "Oops! I did it again";
+ private static final int MAX_VERTICES = 200_000;
+ private static final int MAX_EDGES = 200_000;
static long solve(int n, Edge[] edges) {
+ if (!isValidGraph(n, edges)) {
+ return -1;
+ }
+
Arrays.sort(edges, (a, b) -> Integer.compare(b.weight, a.weight));
DSU dsu = new DSU(n);
@@ -69,6 +75,24 @@ static long solve(int n, Edge[] edges) {
return totalWeight;
}
+ private static boolean isValidGraph(int n, Edge[] edges) {
+ if (n < 1 || n > MAX_VERTICES || edges == null || edges.length > MAX_EDGES) {
+ return false;
+ }
+
+ for (Edge edge : edges) {
+ if (edge == null || !isValidVertex(edge.from, n) || !isValidVertex(edge.to, n)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isValidVertex(int vertex, int n) {
+ return vertex >= 1 && vertex <= n;
+ }
+
static final class Edge {
final int from;
final int to;
@@ -236,17 +260,28 @@ private static void run() throws Exception {
int n = in.nextInt();
int m = in.nextInt();
- Edge[] edges = new Edge[m];
+ long answer = -1;
- for (int i = 0; i < m; i++) {
- int from = in.nextInt();
- int to = in.nextInt();
- int weight = in.nextInt();
+ if (n >= 1 && n <= MAX_VERTICES && m >= 0 && m <= MAX_EDGES) {
+ Edge[] edges = new Edge[m];
+ boolean validEdges = true;
- edges[i] = new Edge(from, to, weight);
- }
+ for (int i = 0; i < m; i++) {
+ int from = in.nextInt();
+ int to = in.nextInt();
+ int weight = in.nextInt();
+
+ if (!isValidVertex(from, n) || !isValidVertex(to, n)) {
+ validEdges = false;
+ }
+
+ edges[i] = new Edge(from, to, weight);
+ }
- long answer = solve(n, edges);
+ if (validEdges) {
+ answer = solve(n, edges);
+ }
+ }
if (answer == -1) {
out.writeString(FAIL);
diff --git a/src/test/java/algorithms/AlgorithmCliTest.java b/src/test/java/algorithms/AlgorithmCliTest.java
index fbe72a5..3a66947 100644
--- a/src/test/java/algorithms/AlgorithmCliTest.java
+++ b/src/test/java/algorithms/AlgorithmCliTest.java
@@ -128,6 +128,12 @@ private static Stream stdioCases() {
arguments("algorithms.sprint6.DorogayaSet", "run",
"4 4%n1 2 5%n1 3 6%n2 4 8%n3 4 3%n".formatted(),
"19%n".formatted()),
+ arguments("algorithms.sprint6.DorogayaSet", "run",
+ "5 -1%n".formatted(),
+ "Oops! I did it again%n".formatted()),
+ arguments("algorithms.sprint6.DorogayaSet", "run",
+ "2 1%n3 1 42%n".formatted(),
+ "Oops! I did it again%n".formatted()),
arguments("algorithms.sprint6.WaterWorld", "run",
"3 3%n#.#%n.#.%n#.#%n".formatted(),
"5 1%n".formatted()),
From d886832e5a151dd78c10146de42826b3ef81f3f0 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:44:38 +0300
Subject: [PATCH 05/18] Harden PyramidSort input bounds
---
.../java/algorithms/sprint5/PyramidSort.java | 20 +++++-
.../algorithms/sprint5/PyramidSortTest.java | 66 +++++++++++++++++++
2 files changed, 85 insertions(+), 1 deletion(-)
create mode 100644 src/test/java/algorithms/sprint5/PyramidSortTest.java
diff --git a/src/main/java/algorithms/sprint5/PyramidSort.java b/src/main/java/algorithms/sprint5/PyramidSort.java
index 37368de..e92fe2d 100644
--- a/src/main/java/algorithms/sprint5/PyramidSort.java
+++ b/src/main/java/algorithms/sprint5/PyramidSort.java
@@ -9,6 +9,8 @@
//https://contest.yandex.ru/contest/24810/run-report/160623687/
public class PyramidSort {
+ private static final int MAX_PARTICIPANTS = 100_000;
+ private static final int MAX_LOGIN_BYTES = 1_024;
/*
* Принцип работы алгоритма:
@@ -147,11 +149,21 @@ int nextInt() throws IOException {
if (c == '-') {
sign = -1;
c = read();
+ if (c <= ' ') {
+ throw new NumberFormatException("Expected digit after sign");
+ }
}
int val = 0;
while (c > ' ') {
- val = val * 10 + c - '0';
+ if (c < '0' || c > '9') {
+ throw new NumberFormatException("Invalid integer input");
+ }
+ int digit = c - '0';
+ if (val > (Integer.MAX_VALUE - digit) / 10) {
+ throw new NumberFormatException("Integer input is too large");
+ }
+ val = val * 10 + digit;
c = read();
}
return val * sign;
@@ -170,6 +182,9 @@ String next() throws IOException {
int n = 0;
while (c > ' ') {
+ if (n == MAX_LOGIN_BYTES) {
+ throw new IOException("Login token is too long");
+ }
if (n == tmp.length) {
byte[] next = new byte[tmp.length * 2];
System.arraycopy(tmp, 0, next, 0, tmp.length);
@@ -219,6 +234,9 @@ private static void run() throws Exception {
FastOut out = new FastOut(System.out);
int n = in.nextInt();
+ if (n < 0 || n > MAX_PARTICIPANTS) {
+ throw new IllegalArgumentException("Participant count is out of range");
+ }
Participant[] a = new Participant[n];
for (int i = 0; i < n; i++) {
diff --git a/src/test/java/algorithms/sprint5/PyramidSortTest.java b/src/test/java/algorithms/sprint5/PyramidSortTest.java
new file mode 100644
index 0000000..cd12398
--- /dev/null
+++ b/src/test/java/algorithms/sprint5/PyramidSortTest.java
@@ -0,0 +1,66 @@
+package algorithms.sprint5;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("unit")
+class PyramidSortTest {
+
+ @Test
+ void fastInRejectsOverflowingInteger() {
+ PyramidSort.FastIn in = fastIn("2147483648 ");
+
+ assertThrows(NumberFormatException.class, in::nextInt);
+ }
+
+ @Test
+ void fastInRejectsOversizedLoginToken() {
+ PyramidSort.FastIn in = fastIn("a".repeat(1_025) + " ");
+
+ assertThrows(java.io.IOException.class, in::next);
+ }
+
+ @Test
+ void runRejectsNegativeParticipantCountBeforeAllocation() throws Exception {
+ InputStream stdin = System.in;
+ System.setIn(new ByteArrayInputStream("-1\n".getBytes(StandardCharsets.UTF_8)));
+ try {
+ Method run = PyramidSort.class.getDeclaredMethod("run");
+ run.setAccessible(true);
+
+ InvocationTargetException ex = assertThrows(InvocationTargetException.class, () -> run.invoke(null));
+
+ assertEquals(IllegalArgumentException.class, ex.getCause().getClass());
+ } finally {
+ System.setIn(stdin);
+ }
+ }
+
+ @Test
+ void solvePreservesParticipantOrdering() {
+ PyramidSort.Participant[] participants = new PyramidSort.Participant[] {
+ new PyramidSort.Participant("alla", 4, 100),
+ new PyramidSort.Participant("gena", 6, 1000),
+ new PyramidSort.Participant("timofey", 4, 80)
+ };
+
+ PyramidSort.solve(participants);
+
+ assertEquals("gena", participants[0].login);
+ assertEquals("timofey", participants[1].login);
+ assertEquals("alla", participants[2].login);
+ }
+
+ private static PyramidSort.FastIn fastIn(String input) {
+ return new PyramidSort.FastIn(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
+ }
+}
From 4c8dc97a75b885fe62ab210a8bd00f05f45e66e1 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:44:44 +0300
Subject: [PATCH 06/18] Harden sprint4 CLI input handling
---
.../java/algorithms/sprint4/FindSystem.java | 44 +++++++++----
src/main/java/algorithms/sprint4/Map.java | 61 ++++++++++++++-----
2 files changed, 79 insertions(+), 26 deletions(-)
diff --git a/src/main/java/algorithms/sprint4/FindSystem.java b/src/main/java/algorithms/sprint4/FindSystem.java
index 4d20189..90f31dc 100644
--- a/src/main/java/algorithms/sprint4/FindSystem.java
+++ b/src/main/java/algorithms/sprint4/FindSystem.java
@@ -9,11 +9,17 @@
import java.util.HashSet;
import java.util.Map;
import java.util.StringTokenizer;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
// https://contest.yandex.ru/contest/24414/run-report/160043341/
class FindSystem {
+ private static final int MAX_DOCUMENTS = 10_000;
+ private static final int MAX_QUERIES = 10_000;
+ private static final int MAX_LINE_LENGTH = 10_000;
+
/*
* Принцип работы алгоритма:
* 1) Строим обратный индекс:
@@ -141,23 +147,24 @@ private static boolean isBetter(int docId1, int score1, int docId2, int score2)
private static void solve() throws Exception {
FastReader reader = new FastReader(System.in);
- int n = reader.nextInt();
+ int n = reader.nextInt(MAX_DOCUMENTS);
String[] docs = new String[n];
for (int i = 0; i < n; i++) {
- docs[i] = reader.nextLine();
+ docs[i] = reader.nextLine(MAX_LINE_LENGTH);
}
HashMap> index = buildIndex(docs);
- int m = reader.nextInt();
- StringBuilder out = new StringBuilder();
+ int m = reader.nextInt(MAX_QUERIES);
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
for (int i = 0; i < m; i++) {
- String query = reader.nextLine();
- out.append(processQuery(query, index)).append('\n');
+ String query = reader.nextLine(MAX_LINE_LENGTH);
+ out.write(processQuery(query, index));
+ out.newLine();
}
- System.out.print(out);
+ out.flush();
}
private static void test() {
@@ -205,7 +212,11 @@ public static void main(String[] args) throws Exception {
if (System.getProperty("os.name").startsWith("Windows")) {
test();
} else {
- solve();
+ try {
+ solve();
+ } catch (IOException ignored) {
+ // Invalid or excessive input is rejected without exhausting memory or CPU.
+ }
}
}
private static class FastReader {
@@ -229,7 +240,7 @@ private int read() throws IOException {
return buffer[ptr++];
}
- int nextInt() throws IOException {
+ int nextInt(int max) throws IOException {
int c;
do {
c = read();
@@ -238,15 +249,21 @@ int nextInt() throws IOException {
}
} while (c <= ' ');
- int value = 0;
+ long value = 0;
while (c > ' ') {
+ if (c < '0' || c > '9') {
+ throw new IOException("Expected a non-negative integer");
+ }
value = value * 10 + c - '0';
+ if (value > max) {
+ throw new IOException("Input value exceeds limit");
+ }
c = read();
}
- return value;
+ return (int) value;
}
- String nextLine() throws IOException {
+ String nextLine(int maxLength) throws IOException {
int c = read();
while (c == '\n' || c == '\r') {
@@ -255,6 +272,9 @@ String nextLine() throws IOException {
StringBuilder sb = new StringBuilder();
while (c != -1 && c != '\n' && c != '\r') {
+ if (sb.length() == maxLength) {
+ throw new IOException("Input line exceeds limit");
+ }
sb.append((char) c);
c = read();
}
diff --git a/src/main/java/algorithms/sprint4/Map.java b/src/main/java/algorithms/sprint4/Map.java
index 4131c92..793d148 100644
--- a/src/main/java/algorithms/sprint4/Map.java
+++ b/src/main/java/algorithms/sprint4/Map.java
@@ -1,9 +1,12 @@
package algorithms.sprint4;
import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStreamWriter;
import java.util.OptionalInt;
+import java.util.concurrent.ThreadLocalRandom;
// https://contest.yandex.ru/contest/24414/run-report/160371601/
@@ -51,6 +54,8 @@ public class Map {
*/
private static final int SIZE = 100_003;
+ private static final int MAX_COMMANDS = 100_000;
+ private static final int HASH_SEED = ThreadLocalRandom.current().nextInt();
static class Node {
int key;
@@ -68,11 +73,9 @@ static class HashTable {
Node[] buckets = new Node[SIZE];
int index(int key) {
- int x = key % SIZE;
- if (x < 0) {
- x += SIZE;
- }
- return x;
+ int mixed = key ^ HASH_SEED;
+ mixed ^= (mixed >>> 16);
+ return Math.floorMod(mixed, SIZE);
}
void put(int key, int value) {
@@ -144,8 +147,15 @@ int read() throws IOException {
}
int nextInt() throws IOException {
+ return nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ int nextInt(int min, int max) throws IOException {
int c = read();
while (c <= ' ') {
+ if (c == -1) {
+ throw new IOException("Unexpected end of input");
+ }
c = read();
}
@@ -155,13 +165,24 @@ int nextInt() throws IOException {
c = read();
}
- int num = 0;
+ if (c < '0' || c > '9') {
+ throw new IOException("Expected integer");
+ }
+
+ long num = 0;
while (c > ' ') {
+ if (c < '0' || c > '9') {
+ throw new IOException("Expected integer");
+ }
num = num * 10 + c - '0';
+ long signed = sign * num;
+ if (signed < min || signed > max) {
+ throw new IOException("Input value exceeds limit");
+ }
c = read();
}
- return num * sign;
+ return (int) (num * sign);
}
char nextCommand() throws IOException {
@@ -181,11 +202,19 @@ char nextCommand() throws IOException {
}
public static void main(String[] args) throws Exception {
+ try {
+ solve();
+ } catch (IOException ignored) {
+ // Invalid or excessive input is rejected without exhausting memory or CPU.
+ }
+ }
+
+ private static void solve() throws IOException {
Reader reader = new Reader();
HashTable table = new HashTable();
- StringBuilder out = new StringBuilder();
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
- int n = reader.nextInt();
+ int n = reader.nextInt(0, MAX_COMMANDS);
for (int i = 0; i < n; i++) {
char command = reader.nextCommand();
@@ -195,14 +224,18 @@ public static void main(String[] args) throws Exception {
int value = reader.nextInt();
table.put(key, value);
} else if (command == 'g') {
- table.get(key).ifPresentOrElse(out::append, () -> out.append("None"));
- out.append(System.lineSeparator());
+ OptionalInt result = table.get(key);
+ out.write(result.isPresent() ? String.valueOf(result.getAsInt()) : "None");
+ out.newLine();
+ } else if (command == 'd') {
+ OptionalInt result = table.delete(key);
+ out.write(result.isPresent() ? String.valueOf(result.getAsInt()) : "None");
+ out.newLine();
} else {
- table.delete(key).ifPresentOrElse(out::append, () -> out.append("None"));
- out.append(System.lineSeparator());
+ throw new IOException("Unknown command");
}
}
- System.out.print(out);
+ out.flush();
}
}
From f493bd507a9927ac5ad4f7d9d46106dcc4701efa Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:45:08 +0300
Subject: [PATCH 07/18] Validate deque capacity before allocation
---
src/main/java/algorithms/sprint2/Deque.java | 34 +++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/src/main/java/algorithms/sprint2/Deque.java b/src/main/java/algorithms/sprint2/Deque.java
index 408d102..2cd8803 100644
--- a/src/main/java/algorithms/sprint2/Deque.java
+++ b/src/main/java/algorithms/sprint2/Deque.java
@@ -55,6 +55,8 @@
public class Deque {
// -------------------- RING BUFFER DEQUE --------------------
+ private static final int MAX_CAPACITY = 100_000;
+
static final class RingDeque {
private final int[] a;
private final int cap;
@@ -63,8 +65,8 @@ static final class RingDeque {
private int size = 0;
RingDeque(int cap) {
- this.cap = cap;
- this.a = new int[cap];
+ this.cap = safeCapacity(cap);
+ this.a = new int[this.cap];
}
private int next(int i) {
@@ -112,6 +114,13 @@ int popBack() {
}
}
+ private static int safeCapacity(int cap) {
+ if (cap < 0) {
+ return 0;
+ }
+ return Math.min(cap, MAX_CAPACITY);
+ }
+
private static void process(FastIn in, FastOut out) throws Exception {
int n = in.nextInt();
int m = in.nextInt();
@@ -235,6 +244,27 @@ private static void test() throws Exception {
)
);
+ // Некорректная емкость из ввода не должна приводить к аварийному завершению
+ assertEq(
+ "error\nerror\n",
+ solveIO(
+ "2\n" +
+ "-1\n" +
+ "push_back 1\n" +
+ "pop_front\n"
+ )
+ );
+
+ // Слишком большая емкость ограничивается безопасным максимумом до выделения массива
+ assertEq(
+ "error\n",
+ solveIO(
+ "1\n" +
+ "1000000000\n" +
+ "pop_front\n"
+ )
+ );
+
// Wrap-around: head/tail должны корректно "перепрыгивать" границу массива
assertEq(
"1\n4\n2\n3\n",
From 0c1b41a9fade0c1eafef702d6319902e31beec0a Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Fri, 31 Jul 2026 17:45:14 +0300
Subject: [PATCH 08/18] Validate SleightOfHand input
---
.../algorithms/sprint1/SleightOfHand.java | 35 +++++++++++++------
1 file changed, 25 insertions(+), 10 deletions(-)
diff --git a/src/main/java/algorithms/sprint1/SleightOfHand.java b/src/main/java/algorithms/sprint1/SleightOfHand.java
index 12b6fbb..1a23981 100644
--- a/src/main/java/algorithms/sprint1/SleightOfHand.java
+++ b/src/main/java/algorithms/sprint1/SleightOfHand.java
@@ -48,18 +48,21 @@ int nextInt() throws IOException {
return val * sign;
}
- String next() throws IOException {
+ String next(int maxLength) throws IOException {
int c;
do {
c = read();
if (c == -1) throw new EOFException("Unexpected EOF");
} while (c <= ' ');
- byte[] tmp = new byte[32];
+ byte[] tmp = new byte[Math.min(32, maxLength)];
int n = 0;
while (c > ' ') {
+ if (n == maxLength) {
+ throw new IOException("Token length exceeds " + maxLength);
+ }
if (n == tmp.length) {
- byte[] t2 = new byte[tmp.length * 2];
+ byte[] t2 = new byte[Math.min(maxLength, tmp.length * 2)];
System.arraycopy(tmp, 0, t2, 0, tmp.length);
tmp = t2;
}
@@ -112,10 +115,14 @@ void flush() throws IOException {
}
public static void main(String[] args) throws Exception {
- if (System.getProperty("os.name").startsWith("Windows")) {
- test();
- } else {
- run();
+ try {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ } catch (IOException | IllegalArgumentException e) {
+ System.err.println(e.getMessage());
}
}
@@ -128,14 +135,17 @@ private static void run() throws Exception {
int[] count = new int[10];
for (int r = 0; r < 4; r++) {
- StringBuilder row = new StringBuilder(in.next());
+ StringBuilder row = new StringBuilder(in.next(4));
// На всякий случай, если токенайзер разделит строку (обычно не будет)
while (row.length() < 4) {
- row.append(in.next());
+ row.append(in.next(4 - row.length()));
}
for (int c = 0; c < 4; c++) {
char ch = row.charAt(c);
if (ch != '.') {
+ if (ch < '0' || ch > '9') {
+ throw new IOException("Invalid grid cell: " + ch);
+ }
count[ch - '0']++;
}
}
@@ -219,7 +229,12 @@ static int solve(int k, int[][] a) {
for (int[] row : a) {
for (int v : row) {
- if (v != 0) count[v]++;
+ if (v != 0) {
+ if (v < 0 || v > 9) {
+ throw new IllegalArgumentException("Grid values must be between 0 and 9");
+ }
+ count[v]++;
+ }
}
}
From f5e142ec0b1e2fb129ec42ba8c2e50972b75448c Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:51:07 +0300
Subject: [PATCH 09/18] Harden Zip CLI input validation
---
src/main/java/algorithms/sprint0/Zip.java | 54 ++++++++++++++++---
src/test/java/algorithms/sprint0/ZipTest.java | 33 ++++++++++++
2 files changed, 79 insertions(+), 8 deletions(-)
diff --git a/src/main/java/algorithms/sprint0/Zip.java b/src/main/java/algorithms/sprint0/Zip.java
index 57dcd85..944e66c 100644
--- a/src/main/java/algorithms/sprint0/Zip.java
+++ b/src/main/java/algorithms/sprint0/Zip.java
@@ -8,15 +8,18 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
+import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static algorithms.sprint0.Utils.printList;
-import static algorithms.sprint0.Utils.readList;
public class Zip {
+ private static final int MAX_LIST_SIZE = 100_000;
+ private static final int MAX_INPUT_LINE_LENGTH = 1_200_001;
+
static List zip(List a, List b, int n) {
if (n < 0) {
throw new IllegalArgumentException("n >= 0 required");
@@ -34,14 +37,49 @@ static List zip(List a, List b, int n) {
public static void main(String[] args) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) {
- String sizeLine = reader.readLine();
- if (sizeLine == null) {
- throw new EOFException("Missing list size");
+ try {
+ process(reader, writer);
+ } catch (IllegalArgumentException | EOFException exception) {
+ System.err.println("Invalid input: " + exception.getMessage());
+ }
+ }
+ }
+
+ static void process(BufferedReader reader, BufferedWriter writer) throws IOException {
+ String sizeLine = readBoundedLine(reader);
+ if (sizeLine == null) {
+ throw new EOFException("Missing list size");
+ }
+ int n = parseInt(sizeLine.trim());
+ if (n < 0 || n > MAX_LIST_SIZE) {
+ throw new IllegalArgumentException("List size must be between 0 and " + MAX_LIST_SIZE);
+ }
+ List a = parseList(readBoundedLine(reader));
+ List b = parseList(readBoundedLine(reader));
+ if (a.size() < n || b.size() < n) {
+ throw new IllegalArgumentException("Each list must contain at least n integers");
+ }
+ printList(zip(a, b, n), writer);
+ }
+
+ private static String readBoundedLine(BufferedReader reader) throws IOException {
+ StringBuilder line = new StringBuilder();
+ int character;
+ while ((character = reader.read()) != -1 && character != '\n') {
+ if (line.length() == MAX_INPUT_LINE_LENGTH) {
+ throw new IllegalArgumentException("Input line is too long");
+ }
+ if (character != '\r') {
+ line.append((char) character);
}
- int n = parseInt(sizeLine.trim());
- List a = readList(reader);
- List b = readList(reader);
- printList(zip(a, b, n), writer);
}
+ return character == -1 && line.length() == 0 ? null : line.toString();
+ }
+
+ private static List parseList(String line) throws IOException {
+ if (line == null) {
+ throw new EOFException("Missing integer list");
+ }
+ return Utils.readList(new BufferedReader(new StringReader(line)));
}
}
diff --git a/src/test/java/algorithms/sprint0/ZipTest.java b/src/test/java/algorithms/sprint0/ZipTest.java
index c97536a..57ef48f 100644
--- a/src/test/java/algorithms/sprint0/ZipTest.java
+++ b/src/test/java/algorithms/sprint0/ZipTest.java
@@ -4,6 +4,11 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Tag;
@@ -48,4 +53,32 @@ void rejectsNullLists() {
assertThrows(NullPointerException.class, () -> Zip.zip(null, List.of(2), 1));
assertThrows(NullPointerException.class, () -> Zip.zip(List.of(1), null, 1));
}
+
+ @Test
+ void processAcceptsValidInput() throws IOException {
+ StringWriter output = new StringWriter();
+ BufferedWriter writer = new BufferedWriter(output);
+
+ Zip.process(new BufferedReader(new StringReader("3\n1 5 6\n7 8 9\n")), writer);
+ writer.flush();
+
+ assertEquals("1 7 5 8 6 9 ", output.toString());
+ }
+
+ @Test
+ void processRejectsMissingOrShortLists() {
+ assertThrows(IOException.class, () -> process("3\n1 2 3\n"));
+ assertThrows(IllegalArgumentException.class, () -> process("3\n1 2\n4 5 6\n"));
+ }
+
+ @Test
+ void processRejectsUnboundedSizesAndLines() {
+ assertThrows(IllegalArgumentException.class, () -> process("100001\n1\n2\n"));
+ String oversizedLine = "1".repeat(1_200_002);
+ assertThrows(IllegalArgumentException.class, () -> process("1\n" + oversizedLine + "\n2\n"));
+ }
+
+ private static void process(String input) throws IOException {
+ Zip.process(new BufferedReader(new StringReader(input)), new BufferedWriter(new StringWriter()));
+ }
}
From 34c8d7df8f7bdd9b4c71ba1684b8e00ad5361835 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:51:42 +0300
Subject: [PATCH 10/18] Reject overflowing digit recompositions
---
src/main/java/kyu7/DescendingOrder.java | 6 +++---
src/main/java/kyu7/MinimumLine.java | 7 +++++--
src/test/java/kyu7/DescendingOrderTest.java | 5 +++++
src/test/java/kyu7/MinimumLineTest.java | 7 +++++++
4 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/src/main/java/kyu7/DescendingOrder.java b/src/main/java/kyu7/DescendingOrder.java
index f896398..46137e9 100644
--- a/src/main/java/kyu7/DescendingOrder.java
+++ b/src/main/java/kyu7/DescendingOrder.java
@@ -18,12 +18,12 @@ public static int sortDesc(int num) {
arrayList.add(i);
}
arrayList.sort(Comparator.reverseOrder());
- int result = 0;
+ StringBuilder result = new StringBuilder();
for (int i : arrayList
) {
- result = result * 10 + i;
+ result.append(i);
}
- return result;
+ return Integer.parseInt(result.toString());
}
}
diff --git a/src/main/java/kyu7/MinimumLine.java b/src/main/java/kyu7/MinimumLine.java
index 8deee48..4c10193 100644
--- a/src/main/java/kyu7/MinimumLine.java
+++ b/src/main/java/kyu7/MinimumLine.java
@@ -2,6 +2,7 @@
import java.util.Arrays;
+import java.util.stream.Collectors;
public class MinimumLine {
@@ -9,10 +10,12 @@ public class MinimumLine {
// 7 https://www.codewars.com/kata/5ac6932b2f317b96980000ca/train/java
public static int minValue(int[] values) {
- return Arrays.stream(values)
+ String result = Arrays.stream(values)
.sorted()
.distinct()
- .reduce(0, (result, digit) -> result * 10 + digit);
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining());
+ return Integer.parseInt(result);
}
}
diff --git a/src/test/java/kyu7/DescendingOrderTest.java b/src/test/java/kyu7/DescendingOrderTest.java
index 3c94032..5afddd7 100644
--- a/src/test/java/kyu7/DescendingOrderTest.java
+++ b/src/test/java/kyu7/DescendingOrderTest.java
@@ -17,6 +17,11 @@
import static kyu7.DescendingOrder.*;
@Tag("smoke")
public class DescendingOrderTest {
+ @Test
+ void rejectsReorderedValuesOutsideIntegerRange() {
+ assertThrows(NumberFormatException.class, () -> sortDesc(2_147_483_647));
+ }
+
@Test
void smokeTestsShouldExecuteApi() {
quality.SmokeMethodTestHarness.verify(kyu7.DescendingOrder.class);
diff --git a/src/test/java/kyu7/MinimumLineTest.java b/src/test/java/kyu7/MinimumLineTest.java
index 7572493..c2f0007 100644
--- a/src/test/java/kyu7/MinimumLineTest.java
+++ b/src/test/java/kyu7/MinimumLineTest.java
@@ -17,6 +17,13 @@
import static kyu7.MinimumLine.*;
@Tag("smoke")
public class MinimumLineTest {
+ @Test
+ void rejectsConcatenatedValuesOutsideIntegerRange() {
+ int[] values = {9, 8, 7, 6, 5, 4, 3, 2, 1, 99};
+
+ assertThrows(NumberFormatException.class, () -> minValue(values));
+ }
+
@Test
void smokeTestsShouldExecuteApi() {
quality.SmokeMethodTestHarness.verify(kyu7.MinimumLine.class);
From 6c999ba5e0926df35bb8d290a1864afeeb093bb8 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:52:03 +0300
Subject: [PATCH 11/18] Fix Unicode case expansion in AdjustCase
---
src/main/java/other/AdjustCase.java | 2 +-
src/test/java/other/AdjustCaseTest.java | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/main/java/other/AdjustCase.java b/src/main/java/other/AdjustCase.java
index aca4865..208b56a 100644
--- a/src/main/java/other/AdjustCase.java
+++ b/src/main/java/other/AdjustCase.java
@@ -13,7 +13,7 @@ public class AdjustCase {
public String adjustCaseToLower(String string) {
if (string == null || string.length() == 0) return string;
if (string.length() == 1) return string.toUpperCase(Locale.ROOT);
- var stringLover = string.toLowerCase(Locale.ROOT).substring(1);
+ var stringLover = string.substring(1).toLowerCase(Locale.ROOT);
var firstChar = string.substring(0, 1).toUpperCase(Locale.ROOT);
return firstChar + stringLover;
}
diff --git a/src/test/java/other/AdjustCaseTest.java b/src/test/java/other/AdjustCaseTest.java
index abb8658..723d0bf 100644
--- a/src/test/java/other/AdjustCaseTest.java
+++ b/src/test/java/other/AdjustCaseTest.java
@@ -17,6 +17,13 @@
import static other.AdjustCase.*;
@Tag("smoke")
public class AdjustCaseTest {
+ @Test
+ void adjustsSuffixWithoutRetainingExpandedLowercaseCharacters() {
+ other.AdjustCase adjustCase = new other.AdjustCase();
+
+ assertEquals("\u0130bc", adjustCase.adjustCaseToLower("\u0130BC"));
+ }
+
@Test
void smokeTestsShouldExecuteApi() {
quality.SmokeMethodTestHarness.verify(other.AdjustCase.class);
From 81ca8bea9fbf8311eb87fe13bbf6b68b9cd9c76a Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:52:29 +0300
Subject: [PATCH 12/18] Restore DigitalRoot legacy API compatibility
---
src/main/java/kyu6/DigitalRoot.java | 12 ++++++++++++
src/test/java/kyu6/DigitalRootTest.java | 1 +
2 files changed, 13 insertions(+)
diff --git a/src/main/java/kyu6/DigitalRoot.java b/src/main/java/kyu6/DigitalRoot.java
index f5af29e..fae3c4a 100644
--- a/src/main/java/kyu6/DigitalRoot.java
+++ b/src/main/java/kyu6/DigitalRoot.java
@@ -28,6 +28,18 @@ public static int digitalRoot(int n) {
return result;
}
+ /**
+ * Retains compatibility with the original Codewars API.
+ *
+ * @param n a non-negative integer
+ * @return the digital root of {@code n}
+ * @deprecated use {@link #digitalRoot(int)} instead
+ */
+ @Deprecated
+ public static int digital_root(int n) {
+ return digitalRoot(n);
+ }
+
/**
* Digital root is the recursive sum of all the digits in a number.
* Given n, take the sum of the digits of n. If that value has more than one
diff --git a/src/test/java/kyu6/DigitalRootTest.java b/src/test/java/kyu6/DigitalRootTest.java
index 5d09716..f6a2668 100644
--- a/src/test/java/kyu6/DigitalRootTest.java
+++ b/src/test/java/kyu6/DigitalRootTest.java
@@ -21,5 +21,6 @@ class DigitalRootTest {
void shouldReduceNumberToSingleDigit(int value, int expected) {
assertEquals(expected, DigitalRoot.digitalRoot(value));
assertEquals(expected, DigitalRoot.digitalRootRecursiveStream(value));
+ assertEquals(expected, DigitalRoot.digital_root(value));
}
}
From 53c7c160b16e8533a3de6ca8f4ee15afc8b3d328 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:52:47 +0300
Subject: [PATCH 13/18] Prevent SquareDigit overflow crash
---
src/main/java/kyu7/SquareDigit.java | 12 ++++++++----
src/test/java/kyu7/SquareDigitTest.java | 10 ++++++++++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/main/java/kyu7/SquareDigit.java b/src/main/java/kyu7/SquareDigit.java
index 5aa3597..e27fe3a 100644
--- a/src/main/java/kyu7/SquareDigit.java
+++ b/src/main/java/kyu7/SquareDigit.java
@@ -1,11 +1,10 @@
package kyu7;
-import static common.SafeParse.parseInt;
-
-
public class SquareDigit {
+ private static final String MAX_INT = Integer.toString(Integer.MAX_VALUE);
+
//7 https://www.codewars.com/kata/546e2562b03326a88e000020/train/java
public static int squareDigits(int n) {
@@ -16,7 +15,12 @@ public static int squareDigits(int n) {
n /= 10;
stringBuilder.insert(0, i * i);
}
- return parseInt(stringBuilder.toString());
+ String result = stringBuilder.toString();
+ if (result.length() > MAX_INT.length()
+ || result.length() == MAX_INT.length() && result.compareTo(MAX_INT) > 0) {
+ return Integer.MAX_VALUE;
+ }
+ return Integer.parseInt(result);
}
}
diff --git a/src/test/java/kyu7/SquareDigitTest.java b/src/test/java/kyu7/SquareDigitTest.java
index e3f663b..26d0e68 100644
--- a/src/test/java/kyu7/SquareDigitTest.java
+++ b/src/test/java/kyu7/SquareDigitTest.java
@@ -17,6 +17,16 @@
import static kyu7.SquareDigit.*;
@Tag("smoke")
public class SquareDigitTest {
+ @Test
+ void squaresDigitsWhenResultFitsInAnInteger() {
+ assertEquals(811181, squareDigits(9119));
+ }
+
+ @Test
+ void capsResultsThatExceedIntegerRange() {
+ assertEquals(Integer.MAX_VALUE, squareDigits(99999));
+ }
+
@Test
void smokeTestsShouldExecuteApi() {
quality.SmokeMethodTestHarness.verify(kyu7.SquareDigit.class);
From abcd9797f19bc43f958bf14ddb88b6cf403345cb Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:53:03 +0300
Subject: [PATCH 14/18] Handle overflow when reversing integers
---
src/main/java/other/ReverseInt.java | 12 ++++--------
src/test/java/other/ReverseIntTest.java | 4 +++-
2 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/main/java/other/ReverseInt.java b/src/main/java/other/ReverseInt.java
index 878428b..e2c6764 100644
--- a/src/main/java/other/ReverseInt.java
+++ b/src/main/java/other/ReverseInt.java
@@ -1,17 +1,13 @@
package other;
-import static common.SafeParse.parseInt;
-
-
public class ReverseInt {
public static int reverse(int x) {
- if (x >= 0) {
- return parseInt(new StringBuilder(String.valueOf(x)).reverse().toString());
- } else {
- return parseInt(new StringBuilder(String.valueOf(x).substring(1)).reverse().toString()) * -1;
- }
+ long reversed = Long.parseLong(new StringBuilder(String.valueOf(Math.abs((long) x))).reverse().toString());
+ reversed = x < 0 ? -reversed : reversed;
+
+ return reversed < Integer.MIN_VALUE || reversed > Integer.MAX_VALUE ? 0 : (int) reversed;
}
}
diff --git a/src/test/java/other/ReverseIntTest.java b/src/test/java/other/ReverseIntTest.java
index 2671789..627fb7e 100644
--- a/src/test/java/other/ReverseIntTest.java
+++ b/src/test/java/other/ReverseIntTest.java
@@ -16,7 +16,9 @@ class ReverseIntTest {
"-123, -321",
"120, 21",
"-120, -21",
- "1000000001, 1000000001"
+ "1000000001, 1000000001",
+ "1534236469, 0",
+ "-2147483648, 0"
})
void shouldReverseIntegerDigits(int input, int expected) {
assertEquals(expected, ReverseInt.reverse(input));
From 7d7f84924e063417d4692d2fc695e1b88e43d4e6 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:53:07 +0300
Subject: [PATCH 15/18] Validate Roman numeral parser input
---
src/main/java/kyu4/RomanNumerals.java | 11 +++++++++--
src/test/java/kyu4/RomanNumeralsTest.java | 19 +++++++++++++++++++
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/src/main/java/kyu4/RomanNumerals.java b/src/main/java/kyu4/RomanNumerals.java
index 3dbf316..4a7c5f4 100644
--- a/src/main/java/kyu4/RomanNumerals.java
+++ b/src/main/java/kyu4/RomanNumerals.java
@@ -84,15 +84,22 @@ public static String toRoman(int n) {
}
public static int fromRoman(String romanNumeral) {
+ if (romanNumeral == null) {
+ throw new IllegalArgumentException("Roman numeral must not be null");
+ }
+
// Traverse from right to left: add when symbol value is >= previous,
// subtract otherwise. This naturally applies the subtractive rule.
StringBuilder stringBuilderReverse = new StringBuilder(romanNumeral).reverse();
char[] chars = stringBuilderReverse.toString().toCharArray();
- int result = 0;
+ long result = 0;
int previous = 0;
for (char c : chars
) {
Integer integer = MAP_FROM.get(c);
+ if (integer == null) {
+ throw new IllegalArgumentException("Unsupported Roman numeral character: " + c);
+ }
if (integer >= previous) {
result += integer;
} else {
@@ -101,7 +108,7 @@ public static int fromRoman(String romanNumeral) {
previous = integer;
}
- return result;
+ return Math.toIntExact(result);
}
diff --git a/src/test/java/kyu4/RomanNumeralsTest.java b/src/test/java/kyu4/RomanNumeralsTest.java
index b2f2904..1685c26 100644
--- a/src/test/java/kyu4/RomanNumeralsTest.java
+++ b/src/test/java/kyu4/RomanNumeralsTest.java
@@ -1,9 +1,11 @@
package kyu4;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.stream.Stream;
import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -30,6 +32,19 @@ void shouldRoundTripCanonicalRomanNumerals(int value) {
assertEquals(value, RomanNumerals.fromRoman(RomanNumerals.toRoman(value)));
}
+ @ParameterizedTest
+ @MethodSource("invalidRomanNumerals")
+ void shouldRejectInvalidRomanNumerals(String romanNumeral) {
+ assertThrows(IllegalArgumentException.class, () -> RomanNumerals.fromRoman(romanNumeral));
+ }
+
+ @Test
+ void shouldRejectResultsOutsideIntegerRange() {
+ String romanNumeral = "M".repeat(2_147_484);
+
+ assertThrows(ArithmeticException.class, () -> RomanNumerals.fromRoman(romanNumeral));
+ }
+
private static Stream romanCases() {
return Stream.of(
Arguments.of(1, "I"),
@@ -45,4 +60,8 @@ private static Stream romanCases() {
Arguments.of(3_999, "MMMCMXCIX")
);
}
+
+ private static Stream invalidRomanNumerals() {
+ return Stream.of((String) null, "A", "MCMX?");
+ }
}
From fefbb9e702ce89a4c757b674f4d2a8d0e900e948 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 04:53:16 +0300
Subject: [PATCH 16/18] Validate resistor values before encoding
---
src/main/java/kyu5/ResistorColorCodes2.java | 13 ++++++++++---
src/test/java/kyu5/ResistorColorCodes2Test.java | 3 +++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/main/java/kyu5/ResistorColorCodes2.java b/src/main/java/kyu5/ResistorColorCodes2.java
index 01522d8..b627ed0 100644
--- a/src/main/java/kyu5/ResistorColorCodes2.java
+++ b/src/main/java/kyu5/ResistorColorCodes2.java
@@ -9,9 +9,13 @@ public class ResistorColorCodes2 {
public static String encodeResistorColors(String ohmsString) {
if (ohmsString == null || !ohmsString.endsWith(" ohms")) return "";
- int resistorOhms = encodeResistorOhms(ohmsString);
- int[] resistorOhmsArr = encodeResistorOhmsToArr(resistorOhms);
- return encodeResistorArrToColor(resistorOhmsArr);
+ try {
+ int resistorOhms = encodeResistorOhms(ohmsString);
+ int[] resistorOhmsArr = encodeResistorOhmsToArr(resistorOhms);
+ return encodeResistorArrToColor(resistorOhmsArr);
+ } catch (IllegalArgumentException e) {
+ return "";
+ }
}
private static int encodeResistorOhms(String ohmsString) {
@@ -22,6 +26,9 @@ private static int encodeResistorOhms(String ohmsString) {
else if (tempArray[0].endsWith("M"))
tempValue = parseDouble(tempArray[0].trim().substring(0, tempArray[0].length() - 1)) * 1_000_000;
else tempValue = parseDouble(tempArray[0].trim());
+ if (!Double.isFinite(tempValue) || tempValue < 10 || tempValue > 990_000_000) {
+ throw new IllegalArgumentException("Resistance must be between 10 and 990M ohms");
+ }
return (int) Math.round(tempValue);
}
diff --git a/src/test/java/kyu5/ResistorColorCodes2Test.java b/src/test/java/kyu5/ResistorColorCodes2Test.java
index 26086c4..d8112bb 100644
--- a/src/test/java/kyu5/ResistorColorCodes2Test.java
+++ b/src/test/java/kyu5/ResistorColorCodes2Test.java
@@ -23,6 +23,9 @@ void shouldRejectMalformedInput() {
assertEquals("", ResistorColorCodes2.encodeResistorColors(null));
assertEquals("", ResistorColorCodes2.encodeResistorColors("47"));
assertEquals("", ResistorColorCodes2.encodeResistorColors("47 ohm"));
+ assertEquals("", ResistorColorCodes2.encodeResistorColors("abc ohms"));
+ assertEquals("", ResistorColorCodes2.encodeResistorColors("-1 ohms"));
+ assertEquals("", ResistorColorCodes2.encodeResistorColors("1e309 ohms"));
}
private static Stream resistorCases() {
From 0e23996d2d4ab05f4c0c37456bfd3f5a604ef485 Mon Sep 17 00:00:00 2001
From: Andrei Ovcharenko
Date: Sat, 1 Aug 2026 11:34:06 +0300
Subject: [PATCH 17/18] Address remaining validation review findings
---
src/main/java/algorithms/sprint0/Zip.java | 10 ++--
.../algorithms/sprint1/SleightOfHand.java | 12 ++---
src/main/java/algorithms/sprint2/Deque.java | 45 ++++++++---------
.../java/algorithms/sprint4/FindSystem.java | 49 ++++++++++++++++---
src/main/java/algorithms/sprint4/Map.java | 4 +-
.../java/algorithms/sprint5/PyramidSort.java | 21 +++++---
.../java/algorithms/sprint6/DorogayaSet.java | 32 ++++++++----
.../sprint7/LevenshteinDistance.java | 25 ++++++----
.../java/algorithms/sprint8/PackedPrefix.java | 25 +++-------
src/main/java/common/SafeParse.java | 20 ++++++++
src/main/java/kyu4/RomanNumerals.java | 6 ++-
src/main/java/kyu5/ResistorColorCodes2.java | 24 ++++++---
src/main/java/kyu6/DigitalRoot.java | 2 -
src/main/java/kyu7/DescendingOrder.java | 4 +-
src/main/java/kyu7/MinimumLine.java | 4 +-
src/main/java/kyu7/SquareDigit.java | 4 +-
src/main/java/other/AdjustCase.java | 42 +++++-----------
src/main/java/other/ReverseInt.java | 8 ++-
src/test/java/algorithms/sprint0/ZipTest.java | 11 +++++
.../java/algorithms/sprint2/DequeTest.java | 26 ++++++++++
.../algorithms/sprint5/PyramidSortTest.java | 10 ++++
.../algorithms/sprint6/DorogayaSetTest.java | 31 ++++++++++++
.../sprint7/LevenshteinDistanceTest.java | 33 +++++++++++++
.../algorithms/sprint8/PackedPrefixTest.java | 20 ++++++++
src/test/java/kyu4/RomanNumeralsTest.java | 2 +-
.../java/kyu5/ResistorColorCodes2Test.java | 1 +
src/test/java/other/AdjustCaseTest.java | 9 ++++
27 files changed, 345 insertions(+), 135 deletions(-)
create mode 100644 src/test/java/algorithms/sprint2/DequeTest.java
create mode 100644 src/test/java/algorithms/sprint6/DorogayaSetTest.java
create mode 100644 src/test/java/algorithms/sprint7/LevenshteinDistanceTest.java
create mode 100644 src/test/java/algorithms/sprint8/PackedPrefixTest.java
diff --git a/src/main/java/algorithms/sprint0/Zip.java b/src/main/java/algorithms/sprint0/Zip.java
index 944e66c..baec5c0 100644
--- a/src/main/java/algorithms/sprint0/Zip.java
+++ b/src/main/java/algorithms/sprint0/Zip.java
@@ -65,12 +65,16 @@ static void process(BufferedReader reader, BufferedWriter writer) throws IOExcep
private static String readBoundedLine(BufferedReader reader) throws IOException {
StringBuilder line = new StringBuilder();
int character;
- while ((character = reader.read()) != -1 && character != '\n') {
+ while ((character = reader.read()) != -1 && character != '\n' && character != '\r') {
if (line.length() == MAX_INPUT_LINE_LENGTH) {
throw new IllegalArgumentException("Input line is too long");
}
- if (character != '\r') {
- line.append((char) character);
+ line.append((char) character);
+ }
+ if (character == '\r') {
+ reader.mark(1);
+ if (reader.read() != '\n') {
+ reader.reset();
}
}
return character == -1 && line.length() == 0 ? null : line.toString();
diff --git a/src/main/java/algorithms/sprint1/SleightOfHand.java b/src/main/java/algorithms/sprint1/SleightOfHand.java
index 1a23981..eff10a0 100644
--- a/src/main/java/algorithms/sprint1/SleightOfHand.java
+++ b/src/main/java/algorithms/sprint1/SleightOfHand.java
@@ -115,14 +115,10 @@ void flush() throws IOException {
}
public static void main(String[] args) throws Exception {
- try {
- if (System.getProperty("os.name").startsWith("Windows")) {
- test();
- } else {
- run();
- }
- } catch (IOException | IllegalArgumentException e) {
- System.err.println(e.getMessage());
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
}
}
diff --git a/src/main/java/algorithms/sprint2/Deque.java b/src/main/java/algorithms/sprint2/Deque.java
index 2cd8803..43b15db 100644
--- a/src/main/java/algorithms/sprint2/Deque.java
+++ b/src/main/java/algorithms/sprint2/Deque.java
@@ -65,7 +65,7 @@ static final class RingDeque {
private int size = 0;
RingDeque(int cap) {
- this.cap = safeCapacity(cap);
+ this.cap = validateCapacity(cap);
this.a = new int[this.cap];
}
@@ -114,16 +114,19 @@ int popBack() {
}
}
- private static int safeCapacity(int cap) {
- if (cap < 0) {
- return 0;
+ private static int validateCapacity(int cap) {
+ if (cap < 0 || cap > MAX_CAPACITY) {
+ throw new IllegalArgumentException("Deque capacity is out of range");
}
- return Math.min(cap, MAX_CAPACITY);
+ return cap;
}
private static void process(FastIn in, FastOut out) throws Exception {
int n = in.nextInt();
int m = in.nextInt();
+ if (n < 0 || n > MAX_CAPACITY) {
+ throw new IllegalArgumentException("Command count is out of range");
+ }
RingDeque dq = new RingDeque(m);
@@ -244,26 +247,9 @@ private static void test() throws Exception {
)
);
- // Некорректная емкость из ввода не должна приводить к аварийному завершению
- assertEq(
- "error\nerror\n",
- solveIO(
- "2\n" +
- "-1\n" +
- "push_back 1\n" +
- "pop_front\n"
- )
- );
-
- // Слишком большая емкость ограничивается безопасным максимумом до выделения массива
- assertEq(
- "error\n",
- solveIO(
- "1\n" +
- "1000000000\n" +
- "pop_front\n"
- )
- );
+ // Некорректная емкость отклоняется, а не меняет заявленную семантику дека.
+ assertRejected("2\n-1\npush_back 1\npop_front\n");
+ assertRejected("1\n1000000000\npop_front\n");
// Wrap-around: head/tail должны корректно "перепрыгивать" границу массива
assertEq(
@@ -291,6 +277,15 @@ static void assertEq(String exp, String act) {
}
}
+ private static void assertRejected(String input) throws Exception {
+ try {
+ solveIO(input);
+ throw new AssertionError("Expected invalid deque capacity to be rejected");
+ } catch (IllegalArgumentException expected) {
+ // Expected validation failure.
+ }
+ }
+
public static void main(String[] args) throws Exception {
if (System.getProperty("os.name").startsWith("Windows")) {
test();
diff --git a/src/main/java/algorithms/sprint4/FindSystem.java b/src/main/java/algorithms/sprint4/FindSystem.java
index 90f31dc..0999637 100644
--- a/src/main/java/algorithms/sprint4/FindSystem.java
+++ b/src/main/java/algorithms/sprint4/FindSystem.java
@@ -11,6 +11,7 @@
import java.util.StringTokenizer;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
// https://contest.yandex.ru/contest/24414/run-report/160043341/
@@ -19,6 +20,9 @@ class FindSystem {
private static final int MAX_DOCUMENTS = 10_000;
private static final int MAX_QUERIES = 10_000;
private static final int MAX_LINE_LENGTH = 10_000;
+ private static final long MAX_TOTAL_DOCUMENT_CHARS = 2_000_000;
+ private static final long MAX_TOTAL_QUERY_CHARS = 2_000_000;
+ private static final long MAX_POSTING_VISITS = 20_000_000;
/*
* Принцип работы алгоритма:
@@ -85,6 +89,17 @@ private static HashMap> buildIndex(String[] docs) {
}
private static String processQuery(String query, HashMap> index) {
+ try {
+ return processQuery(query, index, Long.MAX_VALUE).output();
+ } catch (IOException impossible) {
+ throw new AssertionError(impossible);
+ }
+ }
+
+ private static QueryResult processQuery(
+ String query,
+ HashMap> index,
+ long postingVisitBudget) throws IOException {
HashSet uniqueWords = new HashSet<>();
StringTokenizer st = new StringTokenizer(query);
@@ -93,12 +108,17 @@ private static String processQuery(String query, HashMap relevance = new HashMap<>();
+ long postingVisits = 0;
for (String word : uniqueWords) {
ArrayList docs = index.get(word);
if (docs == null) {
continue;
}
+ if (docs.size() > postingVisitBudget - postingVisits) {
+ throw new IOException("Aggregate query workload exceeds limit");
+ }
+ postingVisits += docs.size();
for (int[] pair : docs) {
int docId = pair[0];
@@ -134,7 +154,10 @@ private static String processQuery(String query, HashMap MAX_TOTAL_DOCUMENT_CHARS) {
+ throw new IOException("Aggregate document input exceeds limit");
+ }
}
HashMap> index = buildIndex(docs);
int m = reader.nextInt(MAX_QUERIES);
- BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
+ BufferedWriter out = new BufferedWriter(
+ new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
+ long queryChars = 0;
+ long postingVisitsLeft = MAX_POSTING_VISITS;
for (int i = 0; i < m; i++) {
String query = reader.nextLine(MAX_LINE_LENGTH);
- out.write(processQuery(query, index));
+ queryChars += query.length();
+ if (queryChars > MAX_TOTAL_QUERY_CHARS) {
+ throw new IOException("Aggregate query input exceeds limit");
+ }
+ QueryResult result = processQuery(query, index, postingVisitsLeft);
+ postingVisitsLeft -= result.postingVisits();
+ out.write(result.output());
out.newLine();
}
@@ -212,11 +249,7 @@ public static void main(String[] args) throws Exception {
if (System.getProperty("os.name").startsWith("Windows")) {
test();
} else {
- try {
- solve();
- } catch (IOException ignored) {
- // Invalid or excessive input is rejected without exhausting memory or CPU.
- }
+ solve();
}
}
private static class FastReader {
diff --git a/src/main/java/algorithms/sprint4/Map.java b/src/main/java/algorithms/sprint4/Map.java
index 793d148..da9637b 100644
--- a/src/main/java/algorithms/sprint4/Map.java
+++ b/src/main/java/algorithms/sprint4/Map.java
@@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
import java.util.OptionalInt;
import java.util.concurrent.ThreadLocalRandom;
@@ -212,7 +213,8 @@ public static void main(String[] args) throws Exception {
private static void solve() throws IOException {
Reader reader = new Reader();
HashTable table = new HashTable();
- BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
+ BufferedWriter out = new BufferedWriter(
+ new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
int n = reader.nextInt(0, MAX_COMMANDS);
diff --git a/src/main/java/algorithms/sprint5/PyramidSort.java b/src/main/java/algorithms/sprint5/PyramidSort.java
index e92fe2d..f4ad429 100644
--- a/src/main/java/algorithms/sprint5/PyramidSort.java
+++ b/src/main/java/algorithms/sprint5/PyramidSort.java
@@ -145,28 +145,33 @@ int nextInt() throws IOException {
}
} while (c <= ' ');
- int sign = 1;
- if (c == '-') {
- sign = -1;
+ boolean negative = c == '-';
+ if (negative) {
c = read();
if (c <= ' ') {
throw new NumberFormatException("Expected digit after sign");
}
}
- int val = 0;
+ int limit = negative ? Integer.MIN_VALUE : -Integer.MAX_VALUE;
+ int multiplyLimit = limit / 10;
+ int value = 0;
while (c > ' ') {
if (c < '0' || c > '9') {
throw new NumberFormatException("Invalid integer input");
}
int digit = c - '0';
- if (val > (Integer.MAX_VALUE - digit) / 10) {
- throw new NumberFormatException("Integer input is too large");
+ if (value < multiplyLimit) {
+ throw new NumberFormatException("Integer input is out of range");
}
- val = val * 10 + digit;
+ value *= 10;
+ if (value < limit + digit) {
+ throw new NumberFormatException("Integer input is out of range");
+ }
+ value -= digit;
c = read();
}
- return val * sign;
+ return negative ? value : -value;
}
String next() throws IOException {
diff --git a/src/main/java/algorithms/sprint6/DorogayaSet.java b/src/main/java/algorithms/sprint6/DorogayaSet.java
index d47b1fb..f2bca3b 100644
--- a/src/main/java/algorithms/sprint6/DorogayaSet.java
+++ b/src/main/java/algorithms/sprint6/DorogayaSet.java
@@ -181,21 +181,35 @@ int nextInt() throws IOException {
}
} while (c <= ' ');
- int sign = 1;
-
- if (c == '-') {
- sign = -1;
+ boolean negative = c == '-';
+ if (negative) {
c = read();
}
- int val = 0;
-
+ int limit = negative ? Integer.MIN_VALUE : -Integer.MAX_VALUE;
+ int multiplyLimit = limit / 10;
+ int value = 0;
+ boolean hasDigit = false;
while (c > ' ') {
- val = val * 10 + c - '0';
+ if (c < '0' || c > '9') {
+ throw new NumberFormatException("Invalid integer input");
+ }
+ int digit = c - '0';
+ if (value < multiplyLimit) {
+ throw new NumberFormatException("Integer input is out of range");
+ }
+ value *= 10;
+ if (value < limit + digit) {
+ throw new NumberFormatException("Integer input is out of range");
+ }
+ value -= digit;
+ hasDigit = true;
c = read();
}
-
- return val * sign;
+ if (!hasDigit) {
+ throw new NumberFormatException("Expected integer");
+ }
+ return negative ? value : -value;
}
}
diff --git a/src/main/java/algorithms/sprint7/LevenshteinDistance.java b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
index 2547124..aa3e61d 100644
--- a/src/main/java/algorithms/sprint7/LevenshteinDistance.java
+++ b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
@@ -108,20 +108,25 @@ String nextLine(int maxLength) throws IOException {
}
while (c != -1 && c != '\n') {
- if (c != '\r') {
- if (size == maxLength) {
- throw new IOException("Input line is too long");
- }
- if (size == tmp.length) {
- byte[] grown = new byte[Math.min(tmp.length * 2, maxLength)];
- System.arraycopy(tmp, 0, grown, 0, tmp.length);
- tmp = grown;
- }
- tmp[size++] = (byte) c;
+ if (size == maxLength + 1) {
+ throw new IOException("Input line is too long");
}
+ if (size == tmp.length) {
+ byte[] grown = new byte[Math.min(tmp.length * 2, maxLength + 1)];
+ System.arraycopy(tmp, 0, grown, 0, tmp.length);
+ tmp = grown;
+ }
+ tmp[size++] = (byte) c;
c = read();
}
+ if (size > 0 && tmp[size - 1] == '\r') {
+ size--;
+ }
+ if (size > maxLength) {
+ throw new IOException("Input line is too long");
+ }
+
return new String(tmp, 0, size, StandardCharsets.UTF_8);
}
}
diff --git a/src/main/java/algorithms/sprint8/PackedPrefix.java b/src/main/java/algorithms/sprint8/PackedPrefix.java
index 859ab05..299750d 100644
--- a/src/main/java/algorithms/sprint8/PackedPrefix.java
+++ b/src/main/java/algorithms/sprint8/PackedPrefix.java
@@ -247,34 +247,23 @@ private static boolean isValidPacked(String packed) {
for (int i = 0; i < packed.length(); i++) {
char current = packed.charAt(i);
- if (current >= 'a' && current <= 'z') {
- continue;
- }
-
- if (current >= '0' && current <= '9') {
+ if (current >= '1' && current <= '9') {
if (i + 1 >= packed.length() || packed.charAt(i + 1) != '[') {
return false;
}
- continue;
- }
-
- if (current == '[') {
- if (i == 0 || packed.charAt(i - 1) < '0' || packed.charAt(i - 1) > '9') {
+ } else if (current == '[') {
+ if (i == 0 || packed.charAt(i - 1) < '1' || packed.charAt(i - 1) > '9') {
return false;
}
top++;
- continue;
- }
-
- if (current == ']') {
- if (top == 0) {
+ } else if (current == ']') {
+ if (top == 0 || packed.charAt(i - 1) == '[') {
return false;
}
top--;
- continue;
+ } else if (current < 'a' || current > 'z') {
+ return false;
}
-
- return false;
}
return top == 0;
diff --git a/src/main/java/common/SafeParse.java b/src/main/java/common/SafeParse.java
index 8c4e14e..c5d9c50 100644
--- a/src/main/java/common/SafeParse.java
+++ b/src/main/java/common/SafeParse.java
@@ -24,4 +24,24 @@ public static double parseDouble(String value) {
throw wrapped;
}
}
+
+ public static int parseUnsignedInt(CharSequence value) {
+ if (value == null || value.length() == 0) {
+ throw new NumberFormatException("Expected unsigned integer");
+ }
+
+ int result = 0;
+ for (int index = 0; index < value.length(); index++) {
+ char current = value.charAt(index);
+ if (current < '0' || current > '9') {
+ throw new NumberFormatException("Invalid unsigned integer: " + value);
+ }
+ int digit = current - '0';
+ if (result > (Integer.MAX_VALUE - digit) / 10) {
+ throw new NumberFormatException("Unsigned integer is out of range: " + value);
+ }
+ result = result * 10 + digit;
+ }
+ return result;
+ }
}
diff --git a/src/main/java/kyu4/RomanNumerals.java b/src/main/java/kyu4/RomanNumerals.java
index 4a7c5f4..2dddf77 100644
--- a/src/main/java/kyu4/RomanNumerals.java
+++ b/src/main/java/kyu4/RomanNumerals.java
@@ -108,7 +108,11 @@ public static int fromRoman(String romanNumeral) {
previous = integer;
}
- return Math.toIntExact(result);
+ int value = Math.toIntExact(result);
+ if (value < 1 || value > 3_999 || !toRoman(value).equals(romanNumeral)) {
+ throw new IllegalArgumentException("Roman numeral is not canonical: " + romanNumeral);
+ }
+ return value;
}
diff --git a/src/main/java/kyu5/ResistorColorCodes2.java b/src/main/java/kyu5/ResistorColorCodes2.java
index b627ed0..8382a90 100644
--- a/src/main/java/kyu5/ResistorColorCodes2.java
+++ b/src/main/java/kyu5/ResistorColorCodes2.java
@@ -4,9 +4,15 @@
import static common.SafeParse.parseDouble;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public class ResistorColorCodes2 {
//5
+ private static final Pattern OHMS_PATTERN =
+ Pattern.compile("([0-9]+(?:\\.[0-9]+)?)([kM]?) ohms");
+
public static String encodeResistorColors(String ohmsString) {
if (ohmsString == null || !ohmsString.endsWith(" ohms")) return "";
try {
@@ -19,13 +25,17 @@ public static String encodeResistorColors(String ohmsString) {
}
private static int encodeResistorOhms(String ohmsString) {
- double tempValue;
- String[] tempArray = ohmsString.split("\\s");
- if (tempArray[0].endsWith("k"))
- tempValue = parseDouble(tempArray[0].trim().substring(0, tempArray[0].length() - 1)) * 1_000;
- else if (tempArray[0].endsWith("M"))
- tempValue = parseDouble(tempArray[0].trim().substring(0, tempArray[0].length() - 1)) * 1_000_000;
- else tempValue = parseDouble(tempArray[0].trim());
+ Matcher matcher = OHMS_PATTERN.matcher(ohmsString);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Invalid resistance format");
+ }
+
+ double tempValue = parseDouble(matcher.group(1));
+ if (matcher.group(2).equals("k")) {
+ tempValue *= 1_000;
+ } else if (matcher.group(2).equals("M")) {
+ tempValue *= 1_000_000;
+ }
if (!Double.isFinite(tempValue) || tempValue < 10 || tempValue > 990_000_000) {
throw new IllegalArgumentException("Resistance must be between 10 and 990M ohms");
}
diff --git a/src/main/java/kyu6/DigitalRoot.java b/src/main/java/kyu6/DigitalRoot.java
index fae3c4a..9dbc467 100644
--- a/src/main/java/kyu6/DigitalRoot.java
+++ b/src/main/java/kyu6/DigitalRoot.java
@@ -33,9 +33,7 @@ public static int digitalRoot(int n) {
*
* @param n a non-negative integer
* @return the digital root of {@code n}
- * @deprecated use {@link #digitalRoot(int)} instead
*/
- @Deprecated
public static int digital_root(int n) {
return digitalRoot(n);
}
diff --git a/src/main/java/kyu7/DescendingOrder.java b/src/main/java/kyu7/DescendingOrder.java
index 46137e9..5b13c5e 100644
--- a/src/main/java/kyu7/DescendingOrder.java
+++ b/src/main/java/kyu7/DescendingOrder.java
@@ -1,6 +1,8 @@
package kyu7;
+import static common.SafeParse.parseUnsignedInt;
+
import java.util.ArrayList;
import java.util.Comparator;
@@ -23,7 +25,7 @@ public static int sortDesc(int num) {
) {
result.append(i);
}
- return Integer.parseInt(result.toString());
+ return parseUnsignedInt(result);
}
}
diff --git a/src/main/java/kyu7/MinimumLine.java b/src/main/java/kyu7/MinimumLine.java
index 4c10193..4cc5615 100644
--- a/src/main/java/kyu7/MinimumLine.java
+++ b/src/main/java/kyu7/MinimumLine.java
@@ -1,6 +1,8 @@
package kyu7;
+import static common.SafeParse.parseUnsignedInt;
+
import java.util.Arrays;
import java.util.stream.Collectors;
@@ -15,7 +17,7 @@ public static int minValue(int[] values) {
.distinct()
.mapToObj(String::valueOf)
.collect(Collectors.joining());
- return Integer.parseInt(result);
+ return parseUnsignedInt(result);
}
}
diff --git a/src/main/java/kyu7/SquareDigit.java b/src/main/java/kyu7/SquareDigit.java
index e27fe3a..a0ac22c 100644
--- a/src/main/java/kyu7/SquareDigit.java
+++ b/src/main/java/kyu7/SquareDigit.java
@@ -1,6 +1,8 @@
package kyu7;
+import static common.SafeParse.parseUnsignedInt;
+
public class SquareDigit {
private static final String MAX_INT = Integer.toString(Integer.MAX_VALUE);
@@ -20,7 +22,7 @@ public static int squareDigits(int n) {
|| result.length() == MAX_INT.length() && result.compareTo(MAX_INT) > 0) {
return Integer.MAX_VALUE;
}
- return Integer.parseInt(result);
+ return parseUnsignedInt(result);
}
}
diff --git a/src/main/java/other/AdjustCase.java b/src/main/java/other/AdjustCase.java
index 208b56a..4fb292a 100644
--- a/src/main/java/other/AdjustCase.java
+++ b/src/main/java/other/AdjustCase.java
@@ -2,7 +2,6 @@
import java.util.Locale;
-import java.util.stream.Collectors;
public class AdjustCase {
@@ -11,41 +10,26 @@ public class AdjustCase {
public static final String EMPTY = "";
public String adjustCaseToLower(String string) {
- if (string == null || string.length() == 0) return string;
- if (string.length() == 1) return string.toUpperCase(Locale.ROOT);
- var stringLover = string.substring(1).toLowerCase(Locale.ROOT);
- var firstChar = string.substring(0, 1).toUpperCase(Locale.ROOT);
- return firstChar + stringLover;
+ return capitalizeFirst(string);
}
public String adjustCaseStream(String string) {
- if (string == null || string.length() == 0) return string;
- var lower = string.chars()
- .mapToObj(i -> (char) i)
- .map(Object::toString)
- .map(value -> value.toLowerCase(Locale.ROOT))
- .collect(Collectors.joining())
- .substring(1);
- return string.chars()
- .mapToObj(i -> (char) i)
- .map(Object::toString)
- .findFirst()
- .orElse("")
- .toUpperCase(Locale.ROOT) + lower;
+ return capitalizeFirst(string);
}
public String adjustCaseFor(String string) {
- if (string == null) return null;
- char[] chars = string.toCharArray();
- for (int i = 0; i < chars.length; i++) {
- if(i == 0) {
- chars[i] = Character.toUpperCase(chars[i]);
- }
- else {
- chars[i] = Character.toLowerCase(chars[i]);
- }
+ return capitalizeFirst(string);
+ }
+
+ private String capitalizeFirst(String string) {
+ if (string == null || string.isEmpty()) {
+ return string;
}
- return String.valueOf(chars);
+ int firstEnd = string.offsetByCodePoints(0, 1);
+ String first = string.substring(0, firstEnd);
+ String lower = string.toLowerCase(Locale.ROOT);
+ int loweredFirstLength = first.toLowerCase(Locale.ROOT).length();
+ return first.toUpperCase(Locale.ROOT) + lower.substring(loweredFirstLength);
}
diff --git a/src/main/java/other/ReverseInt.java b/src/main/java/other/ReverseInt.java
index e2c6764..e380fe5 100644
--- a/src/main/java/other/ReverseInt.java
+++ b/src/main/java/other/ReverseInt.java
@@ -4,8 +4,12 @@
public class ReverseInt {
public static int reverse(int x) {
- long reversed = Long.parseLong(new StringBuilder(String.valueOf(Math.abs((long) x))).reverse().toString());
- reversed = x < 0 ? -reversed : reversed;
+ long reversed = 0;
+ int remaining = x;
+ while (remaining != 0) {
+ reversed = reversed * 10 + remaining % 10;
+ remaining /= 10;
+ }
return reversed < Integer.MIN_VALUE || reversed > Integer.MAX_VALUE ? 0 : (int) reversed;
}
diff --git a/src/test/java/algorithms/sprint0/ZipTest.java b/src/test/java/algorithms/sprint0/ZipTest.java
index 57ef48f..dfe165c 100644
--- a/src/test/java/algorithms/sprint0/ZipTest.java
+++ b/src/test/java/algorithms/sprint0/ZipTest.java
@@ -65,6 +65,17 @@ void processAcceptsValidInput() throws IOException {
assertEquals("1 7 5 8 6 9 ", output.toString());
}
+ @Test
+ void processAcceptsCrOnlyLineEndings() throws IOException {
+ StringWriter output = new StringWriter();
+ BufferedWriter writer = new BufferedWriter(output);
+
+ Zip.process(new BufferedReader(new StringReader("3\r1 3 5\r2 4 6\r")), writer);
+ writer.flush();
+
+ assertEquals("1 2 3 4 5 6 ", output.toString());
+ }
+
@Test
void processRejectsMissingOrShortLists() {
assertThrows(IOException.class, () -> process("3\n1 2 3\n"));
diff --git a/src/test/java/algorithms/sprint2/DequeTest.java b/src/test/java/algorithms/sprint2/DequeTest.java
new file mode 100644
index 0000000..4e3e66f
--- /dev/null
+++ b/src/test/java/algorithms/sprint2/DequeTest.java
@@ -0,0 +1,26 @@
+package algorithms.sprint2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("unit")
+class DequeTest {
+
+ @Test
+ void rejectsCapacityInsteadOfSilentlyClampingIt() {
+ assertThrows(IllegalArgumentException.class, () -> new Deque.RingDeque(100_001));
+ }
+
+ @Test
+ void preservesSupportedCapacity() {
+ Deque.RingDeque deque = new Deque.RingDeque(2);
+ deque.pushBack(1);
+ deque.pushBack(2);
+
+ assertEquals(2, deque.popBack());
+ assertEquals(1, deque.popBack());
+ }
+}
diff --git a/src/test/java/algorithms/sprint5/PyramidSortTest.java b/src/test/java/algorithms/sprint5/PyramidSortTest.java
index cd12398..30c3ca8 100644
--- a/src/test/java/algorithms/sprint5/PyramidSortTest.java
+++ b/src/test/java/algorithms/sprint5/PyramidSortTest.java
@@ -22,6 +22,16 @@ void fastInRejectsOverflowingInteger() {
assertThrows(NumberFormatException.class, in::nextInt);
}
+ @Test
+ void fastInAcceptsMinimumInteger() throws Exception {
+ assertEquals(Integer.MIN_VALUE, fastIn("-2147483648 ").nextInt());
+ }
+
+ @Test
+ void fastInRejectsValuesBelowMinimumInteger() {
+ assertThrows(NumberFormatException.class, () -> fastIn("-2147483649 ").nextInt());
+ }
+
@Test
void fastInRejectsOversizedLoginToken() {
PyramidSort.FastIn in = fastIn("a".repeat(1_025) + " ");
diff --git a/src/test/java/algorithms/sprint6/DorogayaSetTest.java b/src/test/java/algorithms/sprint6/DorogayaSetTest.java
new file mode 100644
index 0000000..6df401c
--- /dev/null
+++ b/src/test/java/algorithms/sprint6/DorogayaSetTest.java
@@ -0,0 +1,31 @@
+package algorithms.sprint6;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("unit")
+class DorogayaSetTest {
+
+ @Test
+ void fastInRejectsOverflowingIntegerTokens() {
+ assertThrows(NumberFormatException.class, () -> fastIn("4294967296 ").nextInt());
+ }
+
+ @Test
+ void fastInAcceptsIntegerBounds() throws Exception {
+ DorogayaSet.FastIn input = fastIn("-2147483648 2147483647 ");
+
+ assertEquals(Integer.MIN_VALUE, input.nextInt());
+ assertEquals(Integer.MAX_VALUE, input.nextInt());
+ }
+
+ private static DorogayaSet.FastIn fastIn(String value) {
+ return new DorogayaSet.FastIn(
+ new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)));
+ }
+}
diff --git a/src/test/java/algorithms/sprint7/LevenshteinDistanceTest.java b/src/test/java/algorithms/sprint7/LevenshteinDistanceTest.java
new file mode 100644
index 0000000..57d0916
--- /dev/null
+++ b/src/test/java/algorithms/sprint7/LevenshteinDistanceTest.java
@@ -0,0 +1,33 @@
+package algorithms.sprint7;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("unit")
+class LevenshteinDistanceTest {
+
+ @Test
+ void stripsOnlyTheOptionalCrLfTerminator() throws IOException {
+ LevenshteinDistance.FastIn input = fastIn("abc\r\n");
+
+ assertEquals("abc", input.nextLine(3));
+ }
+
+ @Test
+ void countsEmbeddedCarriageReturnsTowardTheLimit() {
+ LevenshteinDistance.FastIn input = fastIn("\r\r\r\n");
+
+ assertThrows(IOException.class, () -> input.nextLine(1));
+ }
+
+ private static LevenshteinDistance.FastIn fastIn(String value) {
+ return new LevenshteinDistance.FastIn(
+ new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)));
+ }
+}
diff --git a/src/test/java/algorithms/sprint8/PackedPrefixTest.java b/src/test/java/algorithms/sprint8/PackedPrefixTest.java
new file mode 100644
index 0000000..8fa6eed
--- /dev/null
+++ b/src/test/java/algorithms/sprint8/PackedPrefixTest.java
@@ -0,0 +1,20 @@
+package algorithms.sprint8;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("unit")
+class PackedPrefixTest {
+
+ @Test
+ void rejectsEmptyRepeatBodies() {
+ assertEquals("", PackedPrefix.solve(new String[]{"9[9[9[]]]"}));
+ }
+
+ @Test
+ void rejectsZeroRepeatCounts() {
+ assertEquals("", PackedPrefix.solve(new String[]{"0[a]"}));
+ }
+}
diff --git a/src/test/java/kyu4/RomanNumeralsTest.java b/src/test/java/kyu4/RomanNumeralsTest.java
index 1685c26..4b9d67e 100644
--- a/src/test/java/kyu4/RomanNumeralsTest.java
+++ b/src/test/java/kyu4/RomanNumeralsTest.java
@@ -62,6 +62,6 @@ private static Stream romanCases() {
}
private static Stream invalidRomanNumerals() {
- return Stream.of((String) null, "A", "MCMX?");
+ return Stream.of((String) null, "", "A", "MCMX?", "IIII", "VX", "MMMM");
}
}
diff --git a/src/test/java/kyu5/ResistorColorCodes2Test.java b/src/test/java/kyu5/ResistorColorCodes2Test.java
index d8112bb..ef5bd5b 100644
--- a/src/test/java/kyu5/ResistorColorCodes2Test.java
+++ b/src/test/java/kyu5/ResistorColorCodes2Test.java
@@ -26,6 +26,7 @@ void shouldRejectMalformedInput() {
assertEquals("", ResistorColorCodes2.encodeResistorColors("abc ohms"));
assertEquals("", ResistorColorCodes2.encodeResistorColors("-1 ohms"));
assertEquals("", ResistorColorCodes2.encodeResistorColors("1e309 ohms"));
+ assertEquals("", ResistorColorCodes2.encodeResistorColors("47 garbage ohms"));
}
private static Stream resistorCases() {
diff --git a/src/test/java/other/AdjustCaseTest.java b/src/test/java/other/AdjustCaseTest.java
index 723d0bf..8f15c84 100644
--- a/src/test/java/other/AdjustCaseTest.java
+++ b/src/test/java/other/AdjustCaseTest.java
@@ -24,6 +24,15 @@ void adjustsSuffixWithoutRetainingExpandedLowercaseCharacters() {
assertEquals("\u0130bc", adjustCase.adjustCaseToLower("\u0130BC"));
}
+ @Test
+ void preservesContextSensitiveFinalSigmaAcrossImplementations() {
+ other.AdjustCase adjustCase = new other.AdjustCase();
+
+ assertEquals("Aς", adjustCase.adjustCaseToLower("AΣ"));
+ assertEquals("Aς", adjustCase.adjustCaseStream("AΣ"));
+ assertEquals("Aς", adjustCase.adjustCaseFor("AΣ"));
+ }
+
@Test
void smokeTestsShouldExecuteApi() {
quality.SmokeMethodTestHarness.verify(other.AdjustCase.class);
From 8d82b972ce7086aaf34f77fa86e5ecad9b15127f Mon Sep 17 00:00:00 2001
From: "Andrei.Ovcharenko"
Date: Sat, 1 Aug 2026 12:22:39 +0300
Subject: [PATCH 18/18] fix: preserve valid search workloads
---
.../java/algorithms/sprint4/FindSystem.java | 39 +------------------
1 file changed, 2 insertions(+), 37 deletions(-)
diff --git a/src/main/java/algorithms/sprint4/FindSystem.java b/src/main/java/algorithms/sprint4/FindSystem.java
index 0999637..b084c1e 100644
--- a/src/main/java/algorithms/sprint4/FindSystem.java
+++ b/src/main/java/algorithms/sprint4/FindSystem.java
@@ -20,9 +20,6 @@ class FindSystem {
private static final int MAX_DOCUMENTS = 10_000;
private static final int MAX_QUERIES = 10_000;
private static final int MAX_LINE_LENGTH = 10_000;
- private static final long MAX_TOTAL_DOCUMENT_CHARS = 2_000_000;
- private static final long MAX_TOTAL_QUERY_CHARS = 2_000_000;
- private static final long MAX_POSTING_VISITS = 20_000_000;
/*
* Принцип работы алгоритма:
@@ -89,17 +86,6 @@ private static HashMap> buildIndex(String[] docs) {
}
private static String processQuery(String query, HashMap> index) {
- try {
- return processQuery(query, index, Long.MAX_VALUE).output();
- } catch (IOException impossible) {
- throw new AssertionError(impossible);
- }
- }
-
- private static QueryResult processQuery(
- String query,
- HashMap> index,
- long postingVisitBudget) throws IOException {
HashSet uniqueWords = new HashSet<>();
StringTokenizer st = new StringTokenizer(query);
@@ -108,17 +94,12 @@ private static QueryResult processQuery(
}
HashMap relevance = new HashMap<>();
- long postingVisits = 0;
for (String word : uniqueWords) {
ArrayList docs = index.get(word);
if (docs == null) {
continue;
}
- if (docs.size() > postingVisitBudget - postingVisits) {
- throw new IOException("Aggregate query workload exceeds limit");
- }
- postingVisits += docs.size();
for (int[] pair : docs) {
int docId = pair[0];
@@ -154,10 +135,7 @@ private static QueryResult processQuery(
sb.append(best.get(i)[0]);
}
- return new QueryResult(sb.toString(), postingVisits);
- }
-
- private record QueryResult(String output, long postingVisits) {
+ return sb.toString();
}
private static boolean isBetter(int docId1, int score1, int docId2, int score2) {
@@ -172,13 +150,8 @@ private static void solve() throws Exception {
int n = reader.nextInt(MAX_DOCUMENTS);
String[] docs = new String[n];
- long documentChars = 0;
for (int i = 0; i < n; i++) {
docs[i] = reader.nextLine(MAX_LINE_LENGTH);
- documentChars += docs[i].length();
- if (documentChars > MAX_TOTAL_DOCUMENT_CHARS) {
- throw new IOException("Aggregate document input exceeds limit");
- }
}
HashMap> index = buildIndex(docs);
@@ -186,18 +159,10 @@ private static void solve() throws Exception {
int m = reader.nextInt(MAX_QUERIES);
BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
- long queryChars = 0;
- long postingVisitsLeft = MAX_POSTING_VISITS;
for (int i = 0; i < m; i++) {
String query = reader.nextLine(MAX_LINE_LENGTH);
- queryChars += query.length();
- if (queryChars > MAX_TOTAL_QUERY_CHARS) {
- throw new IOException("Aggregate query input exceeds limit");
- }
- QueryResult result = processQuery(query, index, postingVisitsLeft);
- postingVisitsLeft -= result.postingVisits();
- out.write(result.output());
+ out.write(processQuery(query, index));
out.newLine();
}