HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Project Euler #62 + Java 8 streams

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
streamsprojecteulerjava

Problem

(Inspired by this question)


The digits of the cube, 41063625 (3453), can be permuted to produce
two other cubes:


56623104 (3843) and 66430125 (4053). In fact, 41063625 is the
smallest cube which has exactly three permutations of its digits which
are also cube.


Find the smallest cube for which exactly five permutations of its
digits are cube.

My attempt at #62 using Java 8 streams. I use a custom implementation of Spliterator.OfLong (uncreatively named Generator) that can be programmatically stopped when the Map it is collect()-ing to has the desired results. I also chose a custom Result class to store related information about the generation. For example, the desired result for this challenge is:

----------
Size: 5
Map size: 7772
Last value: 8384
Ratio: 1.0787442099845599
Elapsed time (ms): 16
Result: 127035954683
Values: [5027, 7061, 7202, 8288, 8384]
----------


(This is after one "warm-up" round as shown in the main() method)

  • Is this a feasible approach, or does it really smell 'hack-ish'?



  • Any other room for improvement?



My toKey() method is based on my answer to this question.

`public final class EulerSixtyTwo {
private static final ToLongFunction CUBE =
i -> (long) Math.pow(i.doubleValue(), 3);

private static Long toKey(Long input) {
int[] numbers = new int[10];
for (long i = CUBE.applyAsLong(input); i != 0; i /= 10) {
numbers[(int) (i % 10)]++;
}
int counter = 0;
long result = 0;
for (int i = 0; i toValue(Long input) {
return new ArrayList<>(Collections.singleton(input));
}

private static Result testFor(int limit) {
if (limit > map = generator.longStream().boxed()
.collect(Collectors.toMap(EulerSixtyTwo::toKey, EulerSixtyTwo::toValue,
(a, b) -> {
generator.shouldStop(a.addAll(b) && a.size() == limit);

Solution

I guess, I'll sound rather negative and that's mostly caused by my lack of experience with Java 8.

private static final ToLongFunction CUBE =
        i -> (long) Math.pow(i.doubleValue(), 3);


While this works fine for small values, you'd get round-off errors when they get bigger (presumably somewhere near 1e16, which is a lot, but much lower than Long.MAX_VALUE; and there are many Euler problem needing 1e18). And there's no need to take a chance as

i -> i * i * i;


is both shorter and cleaner. And faster, too.

private static Long toKey(Long input) {
    int[] numbers = new int[10];
    for (long i = CUBE.applyAsLong(input); i != 0; i /= 10) {
        numbers[(int) (i % 10)]++;
    }


Wouldn't "digits" be a better name?

int counter = 0;
    long result = 0;
    for (int i = 0; i < 10; counter += numbers[i++]) {
        result += (int) ((Math.pow(10, numbers[i]) * i - 1) / 9)
                * Math.pow(10, counter);
    }
    return Long.valueOf(result);
}


That was a mystery to me for a few minutes. I guessed, it should do the same as my below method does (I've solved the problem some time ago)

private Multiset toKey(long x) {
    final Multiset result = HashMultiset.create();
    for (; x > 0; x /= 10) result.add(x%10);
    return result;
}


namely create a key based on the digits while ignoring their order. How I understand it, but it could use quite some comments. I'd simply go for this

for (int i = 0; i  0; --j) {
              result = 10 * result + i;
    }
    return Long.valueOf(result);


The following looks like you could use Guava's Multimap, which allows multiple values per key without such strain:

private static List toValue(Long input) {
    return new ArrayList<>(Collections.singleton(input));
}


This is fine here, but shouldn't be used for any serious benchmark

long startTime = System.currentTimeMillis();


The precision is much worse than for nanoTime and mainly: There's no monotonicity guarantee.

Map> map = generator.longStream().boxed()
            .collect(Collectors.toMap(EulerSixtyTwo::toKey, EulerSixtyTwo::toValue,
                        (a, b) -> {
                            generator.shouldStop(a.addAll(b) && a.size() == limit);
                            return a;
                        }));


I see, you're adding the second value to the first and keeping the first, i.e. computing set union. That's fine, but after generator.shouldStop it's the second side-effect in this snippet.

What's worse: You should "find the smallest cube for which exactly five permutations of its digits are cube" and you're finding one having at least five such permutations (AFAIK using == doesn't matter as you stop immediately).

return new Result(toKey(generator.getLastValue()), map, elapsedTime);


I see, extracting the result from the map would be way more complicated.

public void shouldStop(boolean shouldStop) {
        stop = shouldStop;
    }


It's actually a setter, and I'd strongly prefer

public void shouldStop(boolean shouldStop) {
        this.shouldStop = shouldStop;
    }


so I don't have to relate the field, the variable, and the setter names.


Is this a feasible approach, or does it really smell 'hack-ish'?

To me it partly looks like using functional syntax for a not so functional style (I'm not claiming pure functional programming could work for this). I guess, there simply isn't much room for advantageous use of Java 8 features in this problem and that's not your fault.

Code Snippets

private static final ToLongFunction<Long> CUBE =
        i -> (long) Math.pow(i.doubleValue(), 3);
i -> i * i * i;
private static Long toKey(Long input) {
    int[] numbers = new int[10];
    for (long i = CUBE.applyAsLong(input); i != 0; i /= 10) {
        numbers[(int) (i % 10)]++;
    }
int counter = 0;
    long result = 0;
    for (int i = 0; i < 10; counter += numbers[i++]) {
        result += (int) ((Math.pow(10, numbers[i]) * i - 1) / 9)
                * Math.pow(10, counter);
    }
    return Long.valueOf(result);
}
private Multiset<Long> toKey(long x) {
    final Multiset<Long> result = HashMultiset.create();
    for (; x > 0; x /= 10) result.add(x%10);
    return result;
}

Context

StackExchange Code Review Q#92768, answer score: 2

Revisions (0)

No revisions yet.