package net.abhinavsarkar.former; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; /** * https://techdevguide.withgoogle.com/resources/compress-decompression/ */ public class StringDecompression { public static void main(String[] args) { System.out.println(decompress("10[a]")); System.out.println(decompress("3[abc]4[ab]c")); System.out.println(decompress("2[3[a]b]")); System.out.println(decompress("2[]b")); System.out.println(decompress("0[abc]v")); System.out.println(decompress("szs")); } static String decompress(String s) { return parse(s).toString(); } private static CompressedString parse(String s) { Optional> parsed = CompressedString.PARSER.parse(s); if (!parsed.isPresent()) { throw new IllegalArgumentException("Unable to decompress"); } return parsed.get().second; } } class Tuple { A first; B second; Tuple(A first, B second) { this.first = first; this.second = second; } Tuple map(Function f) { return new Tuple<>(first, f.apply(second)); } } @FunctionalInterface interface Parser { Optional> parse(I input); default

Parser map(Function mapper) { return input -> this.parse(input).map(tup -> tup.map(mapper)); } default

Parser seqApply(Parser> parser) { return input -> parser.parse(input) .flatMap(ftup -> this.parse(ftup.first).map(tup -> tup.map(ftup.second))); } default

Parser leftSeqApply(Parser parser) { return this.apply((x, y) -> y, parser); } default

Parser rightSeqApply(Parser parser) { return this.apply((x, y) -> x, parser); } default Parser apply(BiFunction function, Parser parser) { return parser.seqApply(this.map(o -> p -> function.apply(o, p))); } default Parser or(Parser parser) { return input -> this.parse(input).or(() -> parser.parse(input)); } default Parser> many() { return input -> { I rest = input; List output = new ArrayList<>(); Optional> parsed = this.parse(rest); while (parsed.isPresent()) { Tuple tup = parsed.get(); rest = tup.first; output.add(tup.second); parsed = this.parse(rest); } return Optional.of(new Tuple<>(rest, output)); }; } default Parser> some() { return input -> { Tuple> output = many().parse(input).get(); if (output.second.isEmpty()) { return Optional.empty(); } else { return Optional.of(output); } }; } } class CharPredicateParser implements Parser { private Predicate predicate; CharPredicateParser(Predicate predicate) { this.predicate = predicate; } @Override public Optional> parse(String input) { if (input.isEmpty()) { return Optional.empty(); } Character i = input.charAt(0); if (predicate.test(i)) { return Optional.of(new Tuple<>(input.substring(1), i)); } return Optional.empty(); } } class Parsers { public static final Parser DIGIT_PARSER = new CharPredicateParser(Character::isDigit); public static final Parser NUMBER_PARSER = DIGIT_PARSER .some() .map(Parsers::digitsToInt); public static final Parser ENGLISH_LOWER_CASE_CHAR_PARSER = new CharPredicateParser(c -> c >= 97 && c <= 122); public static Parser charParser(Character c) { return new CharPredicateParser(c::equals); } private static Integer digitsToInt(List digits) { int i = 0; for (char c : digits) { i = i * 10 + (c - 48); } return i; } } class CompressedString { private List parts; CompressedString(List parts) { this.parts = parts; } interface Part { String toString(); } @Override public String toString() { return parts.stream() .map(Object::toString) .collect(Collectors.joining()); } public static final Parser PARSER = CompressedPart.PARSER .or(StringPart.PARSER) .many() .map(CompressedString::new); } class StringPart implements CompressedString.Part { private String string; StringPart(String string) { this.string = string; } @Override public String toString() { return this.string; } public static final Parser PARSER = Parsers.ENGLISH_LOWER_CASE_CHAR_PARSER .some() .map(chars -> new StringPart(charListToString(chars))); private static String charListToString(List chars) { return chars.stream() .map(String::valueOf) .collect(Collectors.joining()); } } class CompressedPart implements CompressedString.Part { private int repetition; private CompressedString string; CompressedPart(int repetition, CompressedString string) { this.repetition = repetition; this.string = string; } @Override public String toString() { String s = string.toString(); return Stream.generate(() -> s) .limit(repetition) .collect(Collectors.joining()); } private static final Parser BRACKETED_STRING_PARSER = Parsers.charParser('[') .leftSeqApply(input -> CompressedString.PARSER .rightSeqApply(Parsers.charParser(']')) .parse(input)); public static final Parser PARSER = Parsers.NUMBER_PARSER.apply(CompressedPart::new, BRACKETED_STRING_PARSER); }