/* __ __ __ __ __ ___ * \ \ / / \ \ / / __/ * \ \/ / /\ \ \/ / / * \____/__/ \__\____/__/.ɪᴏ * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ */ package io.vavr.collection.euler; import io.vavr.Tuple; import io.vavr.collection.Map; import io.vavr.collection.Seq; import io.vavr.Tuple2; import io.vavr.collection.List; import io.vavr.collection.Vector; import org.junit.Test; import static io.vavr.API.*; import static io.vavr.collection.Stream.rangeClosed; import static org.assertj.core.api.Assertions.assertThat; public class Euler17Test { /** * <strong>Problem 17: Number letter counts</strong> * <p> * If the numbers 1 to 5 are written out in words: one, two, three, four, * five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total. * <p> * If all the numbers from 1 to 1000 (one thousand) inclusive were written * out in words, how many letters would be used? * <p> * NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and * forty-two) contains 23 letters and 115 (one hundred and fifteen) contains * 20 letters. The use of "and" when writing out numbers is in compliance * with British usage. */ @Test public void shouldSolveProblem17() { runTestsFor(new SolutionA()); runTestsFor(new SolutionB()); // Additional capability, only for more general solution B assertThat(new SolutionB().letterCount(Integer.MAX_VALUE)).isEqualTo("twobilliononehundredandfortysevenmillionfourhundredandeightythreethousandsixhundredandfortyseven".length()); } private static void runTestsFor(SolutionProblem17 solution) { List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty", "twentyone") .zipWithIndex() .forEach(t -> { final int number = t._2 + 1; final String numberAsString = t._1; assertThat(numberAsString).hasSize(solution.letterCount(number)); }); assertThat(solution.letterCount(200)).isEqualTo("twohundred".length()); assertThat(solution.letterCount(201)).isEqualTo("twohundredandone".length()); assertThat(solution.letterCount(342)).isEqualTo(23); assertThat(solution.letterCount(115)).isEqualTo(20); assertThat(solution.letterCount(rangeClosed(1, 5))).isEqualTo(19); assertThat(solution.letterCount(rangeClosed(1, 1000))).isEqualTo(21124); } private interface SolutionProblem17 { int letterCount(int num); default int letterCount(Seq<Integer> range) { return range.map(this::letterCount) .sum().intValue(); } } static final String CONJUNCTION = "and"; static final Map<Integer, String> LENGTHS = List.of( 1, "one", 2, "two", 3, "three", 4, "four", 5, "five", 6, "six", 7, "seven", 8, "eight", 9, "nine", 10, "ten", 11, "eleven", 12, "twelve", 13, "thirteen", 14, "fourteen", 15, "fifteen", 16, "sixteen", 17, "seventeen", 18, "eighteen", 19, "nineteen", 20, "twenty", 30, "thirty", 40, "forty", 50, "fifty", 60, "sixty", 70, "seventy", 80, "eighty", 90, "ninety", 100, "hundred", 1_000, "thousand", 1_000_000, "million", 1_000_000_000, "billion" ).grouped(2).toSortedMap(pair -> Tuple.of((Integer) pair.get(0), (String) pair.get(1))); /** * Solution using Vavr Pattern Matching. */ private static class SolutionA implements SolutionProblem17 { @Override public int letterCount(int num) { return Match(num).of( /*@formatter:off*/ Case($(n -> n >= 1000), n -> length(n / 1000) + length(1000) + letterCount(n % 1000)), Case($(n -> n >= 100), n -> Match(n).of( Case($(n1 -> (n1 % 100) > 0), n1 -> length(n1 / 100) + length(100) + CONJUNCTION.length() + letterCount(n1 % 100)), Case($(), length(n / 100) + length(100)))), Case($(n -> n >= 20), n -> length(n - (n % 10)) + letterCount(n % 10)), Case($(0), 0), Case($(), n -> length(n)) ); /*@formatter:on*/ } private static int length(int number) { return LENGTHS.get(number).map(String::length).get(); } } /** * A more general solution using functionality of the Vavr Collections. */ private static class SolutionB implements SolutionProblem17 { @Override public int letterCount(int number) { return asText(number).length(); } private static String asText(int number) { return LENGTHS.foldRight(Tuple.of(Vector.<String> empty(), number), (magnitudeAndText, lengthsAndRemainder) -> { final int magnitude = magnitudeAndText._1; final int remainder = lengthsAndRemainder._2; return ((remainder >= magnitude) && (remainder > 0)) ? asText(magnitude, magnitudeAndText._2, lengthsAndRemainder._1, remainder) : lengthsAndRemainder; })._1.mkString(); } private static Tuple2<Vector<String>, Integer> asText(int magnitude, String text, Vector<String> chunks, int remainder) { if (remainder >= 100) { text = asText(remainder / magnitude) + text; if ((remainder < 1000) && ((remainder % magnitude) != 0)) { text += CONJUNCTION; } } return Tuple.of(chunks.append(text), remainder % magnitude); } } }