/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.collection.euler;
import io.vavr.Tuple;
import io.vavr.collection.CharSeq;
import io.vavr.Tuple2;
import io.vavr.collection.List;
import org.junit.Test;
import static io.vavr.collection.euler.Utils.factors;
import static org.assertj.core.api.Assertions.assertThat;
public class Euler33Test {
/**
* <strong>Problem 33: Digit cancelling fractions</strong>
* <p>
* The fraction 49/98 is a curious fraction, as an inexperienced
* mathematician in attempting to simplify it may incorrectly believe that
* 49/98 = 4/8, which is correct, is obtained by cancelling the 9s.
* <p>
* We shall consider fractions like, 30/50 = 3/5, to be trivial examples.
* <p>
* There are exactly four non-trivial examples of this type of fraction,
* less than one in value, and containing two digits in the numerator and
* denominator.
* <p>
* If the product of these four fractions is given in its lowest common
* terms, find the value of the denominator.
* <p>
* See also <a href="https://projecteuler.net/problem=33">projecteuler.net
* problem 33</a>.
*/
@Test
public void shouldSolveProblem33() {
assertThat(isNonTrivialDigitCancellingFraction(Tuple.of(49, 98))).isTrue();
assertThat(isNonTrivialDigitCancellingFraction(Tuple.of(30, 50))).isFalse();
assertThat(isNonTrivialDigitCancellingFraction(Tuple.of(21, 22))).isFalse();
assertThat(lowestCommonDenominatorOfProductOfTheFourNonTrivialdigitCancellingFractions()).isEqualTo(100);
}
private static int lowestCommonDenominatorOfProductOfTheFourNonTrivialdigitCancellingFractions() {
return List.rangeClosed(10, 98)
.flatMap(n -> List.rangeClosed(n + 1, 99).map(d -> Tuple.of(n, d)))
.filter(Euler33Test::isNonTrivialDigitCancellingFraction)
.fold(Tuple.of(1, 1), Euler33Test::multiplyFractions)
.apply(Euler33Test::simplifyFraction)._2;
}
private static boolean isNonTrivialDigitCancellingFraction(Tuple2<Integer, Integer> fraction) {
return CharSeq.of(fraction._1.toString())
.filter(d -> d != '0')
.find(d -> CharSeq.of(fraction._2.toString()).contains(d))
.map(d -> fractionCanBeSimplifiedByCancellingDigit(fraction, d))
.getOrElse(false);
}
private static boolean fractionCanBeSimplifiedByCancellingDigit(Tuple2<Integer, Integer> fraction, char d) {
return Tuple.of(CharSeq.of(fraction._1.toString()).remove(d), CharSeq.of(fraction._2.toString()).remove(d))
.map(CharSeq::mkString, CharSeq::mkString)
.map(Double::valueOf, Double::valueOf)
.apply((d1, d2) -> fraction._1 / d1 == fraction._2 / d2);
}
private static Tuple2<Integer, Integer> multiplyFractions(Tuple2<Integer, Integer> f1, Tuple2<Integer, Integer> f2) {
return Tuple.of(f1._1 * f2._1, f1._2 * f2._2);
}
private static Tuple2<Integer, Integer> simplifyFraction(int numerator, int denominator) {
return factors(numerator)
.map(Long::intValue)
.filter(f -> f != 1)
.sorted()
.findLast(f -> denominator % f == 0)
.map(f -> simplifyFraction(numerator / f, denominator / f))
.getOrElse(Tuple.of(numerator, denominator));
}
}