This Week in Dev

Java, Streams and Loops - Affine Cipher

import java.math.BigInteger;

class AffineCipher {

    private static final int M = 26;        // size of alphabet
    private static final int A = (int) 'a'; // zero-index of 'a' (ASCII code point 97)

    String encode(String input, int a, int b) {
        if (greatestCommonDivisor(a, M) != 1) {
            throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
        }

        StringBuilder output = new StringBuilder();

        for (int codePoint :
            input.replaceAll("\\W", "") // nix punctuation and whitespace
                    .toLowerCase()
                    .toCharArray()
        ) {
            output.append( Character.isDigit(codePoint)
                    ? (char) codePoint
                    : (char) (((a * (codePoint - A) + b) % M) + A));
        }

        return String.join(" ", splitString(output.toString(), 5));
    }

    String decode(String input, int a, int b) {
        if (greatestCommonDivisor(a, M) != 1) {
            throw new IllegalArgumentException("Error: keyA and alphabet size must be coprime.");
        }

        int mmiOfa = modularMultiplicativeInverse(a, M);

        return input.replaceAll("\\s", "") // nix whitespace
                .chars()
                .map(codePoint ->
                        Character.isDigit(codePoint)
                                ? codePoint
                                : mmiOfa * (codePoint - A + M*b - b) % M + A)
                .collect(StringBuilder::new,
                        (sb, i) -> sb.append((char) i),
                        StringBuilder::append)
                .toString();
    }

    private int modularMultiplicativeInverse(int a, int m) {
        return BigInteger.valueOf(a)
                .modInverse(BigInteger.valueOf(m))
                .intValue();
    }

    private String[] splitString(String inputString, int segmentSize) {
        String re = "(?<=\\G.{" + segmentSize + "})";

        return inputString.split(re);
    }

    private static int greatestCommonDivisor(int a, int m) {
        return (m == 0) ? a // base case

                : greatestCommonDivisor(m, a % m);
    }

}