-2

How would one re-write the following in proper Java 8 functional style using filter, collector, etc:

private BigInteger calculateProduct(char[] letters) {

    int OFFSET = 65;

    BigInteger[] bigPrimes = Arrays.stream(
            new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
            37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
            97, 101, 103, 107, 109, 113 })
            .mapToObj(BigInteger::valueOf)
            .toArray(BigInteger[]::new);


    BigInteger result = BigInteger.ONE;
    for (char c : letters) {
        //System.out.println(c+"="+(int)c);
        if (c < OFFSET) {
            return new BigInteger("-1");
        }
        int pos = c - OFFSET;
        result = result.multiply(bigPrimes[pos]);
    }
    return result;
}


@Test public void test() {
    assertThat(calculateProduct(capitalize("carthorse"))).isEqualTo(calculateProduct(capitalize("orchestra")));

}


private char[] capitalize(String word) {
    return word.toUpperCase().toCharArray();
}
Simeon Leyzerzon
  • 18,658
  • 9
  • 54
  • 82

2 Answers2

2

I can't tell why you want that, but may be this (which creates many more objects and is more verbose):

private static BigInteger calculateProduct(char[] letters) {

    int OFFSET = 65;

    BigInteger[] bigPrimes = Arrays.stream(
            new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
                    37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
                    97, 101, 103, 107, 109, 113 })
            .mapToObj(BigInteger::valueOf)
            .toArray(BigInteger[]::new);

    Optional<Character> one = IntStream.range(0, letters.length)
            .mapToObj(x -> letters[x])
            .filter(x -> x < OFFSET)
            .findAny();

    if (one.isPresent()) {
        return new BigInteger("-1");
    } else {
        return IntStream.range(0, letters.length)
                .mapToObj(x -> letters[x])
                .parallel()
                .reduce(
                        BigInteger.ONE,
                        (x, y) -> {
                            int pos = y - OFFSET;
                            return x.multiply(bigPrimes[pos]);
                        },
                        BigInteger::multiply);
    }

}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Why are you multiplying `BigInteger.ONE` to the result in every evaluation? Multiplying something with one doesn’t change the result, that applies `BigInteger` as well (and the OP’s code doesn’t do that). And generally, …why so complicated? There is no need to handle `char` values as `Character`, *especially* as there’s only arithmetic done on them. – Holger Oct 04 '17 at 11:50
  • @Holger you're totally right, I entirely missed that – Eugene Oct 04 '17 at 12:02
2

You may do it like this:

static final BigInteger[] PRIMES
    = IntStream.of(
        2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
        59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113)
    .mapToObj(BigInteger::valueOf)
    .toArray(BigInteger[]::new);

private BigInteger calculateProduct(char[] letters) {
    final int OFFSET = 65;
    final CharBuffer cb = CharBuffer.wrap(letters);
    if(cb.chars().anyMatch(c -> c<OFFSET))
        return BigInteger.ONE.negate();
    return cb.chars()
        .mapToObj(c -> PRIMES[c-OFFSET])
        .reduce(BigInteger.ONE, BigInteger::multiply);
}

Note that moving the creation of the PRIMES array out of the method, to avoid generating it again for each invocation, is an improvement that works independently of whether you use loops or “functional style” operations.

Also, your code doesn’t handle characters being too large, so you might improve the method to

private BigInteger calculateProduct(char[] letters) {
    final int OFFSET = 65;
    final CharBuffer cb = CharBuffer.wrap(letters);
    if(cb.chars().mapToObj(c -> c-OFFSET).anyMatch(c -> c<0||c>PRIMES.length))
        return BigInteger.ONE.negate();
    return cb.chars()
        .mapToObj(c -> PRIMES[c-OFFSET])
        .reduce(BigInteger.ONE, BigInteger::multiply);
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • oh this is clean! – Eugene Oct 04 '17 at 12:06
  • I guess the parameter array `char[] letters` could also be made `final`, right? – Simeon Leyzerzon Feb 06 '18 at 02:13
  • Yes, you could declare `letters` as `final` too. But that’s just a stylistic choice, without any impact on the code (it’s already *effectively final*). The only variable where it has an impact, is the `OFFSET` variable, which is a compile-time constant when it has the `final` modifier. – Holger Feb 06 '18 at 09:21