Last active
November 26, 2023 21:54
-
-
Save avorobyov/087ad2646737dfb7c20c6717e51a6c8b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.demo; | |
import org.junit.jupiter.api.Assertions; | |
import org.junit.jupiter.api.Test; | |
import org.junit.jupiter.params.ParameterizedTest; | |
import org.junit.jupiter.params.provider.ValueSource; | |
import java.math.BigInteger; | |
import java.nio.charset.StandardCharsets; | |
import java.util.Objects; | |
// Encode (and decode) any string to a string only with a-z characters | |
public final class Base26 { | |
private static final BigInteger BASE = BigInteger.valueOf(26); | |
private static final char smallA = 'a'; | |
private static final char[] letters = "abcdefghijklmnopqrstuvwxyz".toCharArray(); | |
private static final String EMPTY_STRING = ""; | |
private Base26() { | |
// empty | |
} | |
public static String encode(String input) { | |
Objects.requireNonNull(input, "parameter 'input' cannot be null"); | |
if (input.isEmpty()) { | |
return EMPTY_STRING; | |
} | |
StringBuilder buffer = new StringBuilder(); | |
BigInteger number = new BigInteger(input.getBytes(StandardCharsets.UTF_8)); | |
BigInteger[] resultAndReminder; | |
do { | |
resultAndReminder = number.divideAndRemainder(BASE); | |
BigInteger reminder = resultAndReminder[1]; | |
BigInteger result = resultAndReminder[0]; | |
char c = toCharacter(reminder.intValue()); | |
buffer.append(c); | |
number = result; | |
} while (isGreaterThan26(number)); | |
buffer.append(toCharacter(number.intValue())); | |
return buffer.reverse().toString(); | |
} | |
public static String decode(String input) { | |
Objects.requireNonNull(input, "parameter 'input' cannot be null"); | |
if (input.isEmpty()) { | |
return EMPTY_STRING; | |
} | |
// let's do a quick validation before doing expensive long multiplication | |
requireBase26(input); | |
int c = input.charAt(0) - smallA; | |
BigInteger result = BigInteger.valueOf(c); | |
for (int i = 1; i < input.length(); i++) { | |
c = input.charAt(i) - smallA; | |
result = result.multiply(BASE); | |
result = result.add(BigInteger.valueOf(c)); | |
} | |
return new String(result.toByteArray()); | |
} | |
private static void requireBase26(String s) { | |
for (int i = 0; i < s.length(); i++) { | |
char c = s.charAt(i); | |
if (c < 'a' || c > 'z') { | |
throw new IllegalArgumentException("string is not in Base26 format"); | |
} | |
} | |
} | |
private static char toCharacter(int position) { | |
return letters[position]; | |
} | |
private static boolean isGreaterThan26(BigInteger number) { | |
return number.min(BASE).equals(BASE); | |
} | |
} | |
class Base26Tests { | |
@ParameterizedTest | |
@ValueSource(strings = { | |
"Let’s go to the zoo 384 kдфлоадып 我们一起去动物园吧!فارسی \uD83D\uDC7D", | |
"" // corner case | |
}) | |
void encodedContainsOnlyCharsFromAtoZ(String input) { | |
System.out.println(input); | |
String output = Base26.encode(input); | |
System.out.println(output); | |
for (char c: output.toCharArray()) { | |
Assertions.assertTrue(c >= 'a' && c <='z'); | |
} | |
} | |
@ParameterizedTest | |
@ValueSource(strings = { | |
"Let’s go to the zoo 384 kдфлоадып 我们一起去动物园吧!فارسی \uD83D\uDC7D", | |
"" // corner case | |
}) | |
void decode(String input) { | |
System.out.println(input); | |
String output = Base26.encode(input); | |
System.out.println(output); | |
Assertions.assertEquals(input, Base26.decode(output)); | |
} | |
@ParameterizedTest | |
@ValueSource(strings = {"A", "sld8", "sd o", "ш"}) | |
void throwExceptionIfInputIsNotBase26(String input) { | |
IllegalArgumentException thrown = Assertions.assertThrows(IllegalArgumentException.class, () -> { | |
Base26.decode(input); | |
}); | |
Assertions.assertEquals("string is not in Base26 format", thrown.getMessage()); | |
} | |
@Test | |
void encodeThrowsNpe() { | |
NullPointerException thrown = Assertions.assertThrows(NullPointerException.class, () -> { | |
Base26.encode(null); | |
}); | |
Assertions.assertEquals("parameter 'input' cannot be null", thrown.getMessage()); | |
} | |
@Test | |
void decodeThrowsNpe() { | |
NullPointerException thrown = Assertions.assertThrows(NullPointerException.class, () -> { | |
Base26.decode(null); | |
}); | |
Assertions.assertEquals("parameter 'input' cannot be null", thrown.getMessage()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment