14

I was experimenting with this question today, from Euler Problems:

A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.

Find the largest palindrome made from the product of two 3-digit numbers.

I thought about it and it can of course be done with for-loops, however I want to use Java 8 as it opens up new options.

However first of all, I do not know how to generate an IntStream that produces such elements, so I still ended up using normal for-loops:

public class Problem4 extends Problem<Integer> {
    private final int digitsCount;

    private int min;
    private int max;

    public Problem4(final int digitsCount) {
        this.digitsCount = digitsCount;
    }

    @Override
    public void run() {
        List<Integer> list = new ArrayList<>();
        min = (int)Math.pow(10, digitsCount - 1);
        max = min * 10;

        for (int i = min; i < max; i++) {
            for (int j = min; j < max; j++) {
                int sum = i * j;
                if (isPalindrome(sum)) {
                    list.add(sum);
                }
            }
        }

        result = list.stream().mapToInt(i -> i).max().getAsInt();
    }

    private boolean isPalindrome(final int number) {
        String numberString = String.valueOf(number);
        String reversed = new StringBuilder(numberString).reverse().toString();
        return (numberString.equals(reversed));
    }

    @Override
    public String getName() {
        return "Problem 4";
    }
}

As you can see I might be a bit lazy, bit really the IntStream::max is a very nice method and I think it is better to use that, as to write it yourself.

Here comes the issue though, I need to have a list now to be able to obtain the maximum in this manner, which means I need to store data, where I really should not do so.

So, the question now, would it be possible to implement this in Java 8?

for (int i = min; i < max; i++) {
    for (int j = min; j < max; j++) {
        yield i * j;
    }
}

And then out of that method create an PrimitiveIterator.OfInt (unboxes version of Iterator<Integer>, or create an IntStream directly?
Then getting the answer with streamFromYield.filter(this::isPalindrome).max().getAsInt() would be really easy to implement.

Lastly, I know this question has been asked before, however the last time is already quite a bit ago and now Java 8 is going to happen very soon, where they have added as big concept Stream<T> and the new language construct, called lambdas.
So making such code may be very different now than when people were making it for Java 6 or 7.

Community
  • 1
  • 1
skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 1
    It's probably quicker to `int maxNumber = 999 * 999; for (int i = maxNumber; i > 0; i--) { if (isPalindrome(i) && has3DigitsFactors(i)) { System.out.println(i); break; } }` in the first place – assylias Feb 23 '14 at 18:47
  • @assylias I've implemented that before this solution, however perhaps my `has3DigitsFactors` was not really good, so if you could provide a fast version to check the factor, then it's fine, else it is not really an option. – skiwi Feb 23 '14 at 18:52

4 Answers4

8

Well, I think we've gotten carried away using the Streams API from the "outside," using flatMap, optimizing the palindrome-finding algorithm, etc. See answers from Boris the Spider and assylias. However, we've sidestepped the original question of how to write a generator function using something like Python's yield statement. (I think the OP's nested-for example with yield was using Python.)

One of the problems with using flatMap is that parallel splitting can only occur on the outermost stream. The inner streams (returned from flatMap) are processed sequentially. We could try to make the inner streams also parallel, but they'd possibly compete with the outer ones. I suppose nested splitting could work, but I'm not too confident.

One approach is to use the Stream.generate or (like assylias' answer) the Stream.iterate functions. These create infinite streams, though, so an external limit must be supplied to terminate the stream.

It would be nice if we could create a finite but "flattened" stream so that the entire stream of values is subject to splitting. Unfortunately creating a stream is not nearly as convenient as Python's generator functions. It can be done without too much trouble, though. Here's an example that uses the StreamSupport and AbstractSpliterator classes:

class Generator extends Spliterators.AbstractIntSpliterator {
    final int min;
    final int max;
    int i;
    int j;

    public Generator(int min, int max) {
        super((max - min) * (max - min), 0);
        this.min = min;
        this.max = max;
        i = min;
        j = min;
    }

    public boolean tryAdvance(IntConsumer ic) {
        if (i == max) {
            return false;
        }
        ic.accept(i * j);
        j++;
        if (j == max) {
            i++;
            j = min;
        }
        return true;
    }
}

public static void main(String[] args) {
    Generator gen = new Generator(100, 1000);
    System.out.println(
        StreamSupport.intStream(gen, false)
            .filter(i -> isPalindrome(i))
            .max()
            .getAsInt());
}

Instead of having the iteration variables be on the stack (as in the nested-for with yield approach) we have to make them fields of an object and have the tryAdvance increment them until the iteration is complete. Now, this is the simplest form of a spliterator and it doesn't necessarily parallelize well. With additional work one could implement the trySplit method to do better splitting, which in turn would enable better parallelism.

The forEachRemaining method could be overridden, and it would look almost like the nested-for-loop-with-yield example, calling the IntConsumer instead of yield. Unfortunately tryAdvance is abstract and therefore must be implemented, so it's still necessary to have the iteration variables be fields of an object.

Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • I like your attempt, however the whole point was to not put the variables as fields of the object, because in my opinion it makes calculations quite cumbersome compared to the yield-method. As other sidenode, this could also be implemented as a normal `Iterator`. No offence at all for trying to help me out though. – skiwi Feb 24 '14 at 09:26
3

How about looking at it from another direction:

You want a Stream of [100,1000), and for each element of that Stream you want another Stream of that element multiplied by each of [100, 1000). This is what flatMap is for:

public static void main(final String[] args) throws Exception {
    OptionalInt max = IntStream.range(100, 1000).
            flatMap((i) -> IntStream.range(i, 1000).map((j) -> i * j)).
            unordered().
            parallel().
            filter((i) -> {
                String forward = Integer.toString(i);
                String backward = new StringBuilder(forward).reverse().toString();
                return forward.equals(backward);
            }).
            max();
    System.out.println(max);
}

Not sure if getting a String and then the reverse is the most efficient way to detect palindromes, off the top of my head this would seem to be faster:

final String asString = Integer.toString(i);
for (int j = 0, k = asString.length() - 1; j < k; j++, k--) {
    if (asString.charAt(j) != asString.charAt(k)) {
        return false;
    }
}
return true;

It gives the same answer but I haven't put it under an rigorous testing... Seems to be about 100ms faster on my machine.

Also not sure this problem is big enough for unordered().parallel() - removing that gives a little boost to speed too.

Was just trying to demonstrate the capabilities of the Stream API.

EDIT

As @Stuart points out in the comments, as multiplication is commutative, we only need to IntStream.range(i, 1000) in the sub-stream. This is because once we check a x b we don't need to check b x a. I have updated the answer.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • Of course... I had been looking at `flatMap` already, but could find no way to actually multiply the elements, but this is how it should be used then. – skiwi Feb 23 '14 at 18:56
  • @skiwi `flatMap` is one of those magic functions that can do all sorts of things but it's not always obvious how. Playing around with functional idioms will help give you a few ideas ;). – Boris the Spider Feb 23 '14 at 19:00
  • 1
    This is significantly slower (~3x) than the approach I proposed. – assylias Feb 23 '14 at 19:05
  • @assylias why do you reckon that is? I suppose it's the `flatMap` creating the sub-streams. Come to think about it it's probably because you loop backwards - `906609` is towards the end. – Boris the Spider Feb 23 '14 at 19:07
  • @BoristheSpider I'm not sure to be honest - but one difference is that the first occurence I find in my algo is the one and I can break - whereas you calculate many unused products/palindromes to only select the max number. – assylias Feb 23 '14 at 19:09
  • Certainly the `flatMap` approach is the most idiomatic for streams. I would have posted something like this but you beat me to it. :-) +1. One point is that stream returned from `flatMap` can go from `i` to 1000 instead of 100 to 1000, so you can cut down half the search space that way. – Stuart Marks Feb 23 '14 at 19:13
  • 1
    On parallel performance, the search space has only about (1000-100)^2/2 = 405,000 items, so it's small enough that there might not be any parallel speedup. Even a sequential run takes only 200ms on my old, slow machine. Running in parallel doesn't seem to make a difference. The size of the outer `IntRange` might be below the split threshold. – Stuart Marks Feb 23 '14 at 19:22
  • @StuartMarks thanks - looping over the whole lot is a schoolboy error! You're right about the parallelism, I think you need a lot more work in the iterations or many more iterations to make that worthwhile. – Boris the Spider Feb 23 '14 at 19:30
3

There always have been ways to emulate that overrated yield feature, even without Java 8. Basically it is about storing the state of an execution, i.e. the stack frame(s), which can be done by a thread. A very simple implementation could look like this:

import java.util.Iterator;
import java.util.NoSuchElementException;

public abstract class Yield<E> implements Iterable<E> {
  protected interface Flow<T> { void yield(T item); }
  private final class State implements Runnable, Iterator<E>, Flow<E> {
    private E nextValue;
    private boolean finished, value;

    public synchronized boolean hasNext() {
      while(!(value|finished)) try { wait(); } catch(InterruptedException ex){}
      return value;
    }
    public synchronized E next() {
      while(!(value|finished)) try { wait(); } catch(InterruptedException ex){}
      if(!value) throw new NoSuchElementException();
      final E next = nextValue;
      value=false;
      notify();
      return next;
    }
    public void remove() { throw new UnsupportedOperationException(); }
    public void run() {
      try { produce(this); }
      finally {
        synchronized(this) {
          finished=true;
          notify();
        }
      }
    }
    public synchronized void yield(E next) {
      while(value) try { wait(); } catch(InterruptedException ex){}
      nextValue=next;
      value=true;
      notify();
    }
  }

  protected abstract void produce(Flow<E> f);

  public Iterator<E> iterator() {
    final State state = new State();
    new Thread(state).start();
    return state;
  }
}

Once you have such a helper class, the use case will look straight-forward:

// implement a logic the yield-style
Iterable<Integer> y=new Yield<Integer>() {
  protected void produce(Flow<Integer> f) {

    for (int i = min; i < max; i++) {
      for (int j = min; j < max; j++) {
          f.yield(i * j);
      }
    }

  }
};

// use the Iterable, e.g. in a for-each loop
int maxPalindrome=0;
for(int i:y) if(isPalindrome(i) && i>maxPalindrome) maxPalindrome=i;
System.out.println(maxPalindrome);

The previous code didn’t use any Java 8 features. But it will allow using them without the need for any change:

// the Java 8 way
StreamSupport.stream(y.spliterator(), false).filter(i->isPalindrome(i))
  .max(Integer::compare).ifPresent(System.out::println);

Note that the Yield support class above is not the most efficient implementation and it doesn’t handle the case if an iteration is not completed but the Iterator abandoned. But it shows that such a logic is indeed possible to implement in Java (while the other answers convincingly show that such a yield logic is not necessary to solve such a problem).

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I see what you are trying to do - I just don't quite get that the `Thread` is for. It seems to asynchronously produce items for the `Iterator` to consume but why is that necessary? All of the code surrounding the `Thread` is blocking so surely you can just use a peeking `Iterator` pattern? – Boris the Spider Feb 26 '14 at 08:37
  • @Boris the Spider: Well, as said, there is no real need for the `yield` pattern. But if you want it, it is about allowing the execution of *arbitrary code* which could pass new items at arbitrary points; it doesn’t have to be a loop. So the thread remembers the execution state of the code which invokes `yield` and can continue right after the most recent `yield` statement once the item has been consumed. – Holger Feb 26 '14 at 08:51
1

I'll give it a go. Version with a loop then with a stream. Although I start from the other end so it's easier because I can limit(1).

public class Problem0004 {

    public static void main(String[] args) {
        int maxNumber = 999 * 999;
        //with a loop
        for (int i = maxNumber; i > 0; i--) {
            if (isPalindrome(i) && has3DigitsFactors(i)) {
                System.out.println(i);
                break;
            }
        }
        //with a stream
        IntStream.iterate(maxNumber, i -> i - 1)
                .parallel()
                .filter(i -> isPalindrome(i) && has3DigitsFactors(i))
                .limit(1)
                .forEach(System.out::println);
    }

    private static boolean isPalindrome(int n) {
        StringBuilder numbers = new StringBuilder(String.valueOf(n));
        return numbers.toString().equals(numbers.reverse().toString());
    }

    private static boolean has3DigitsFactors(int n) {
        for (int i = 999; i > 0; i--) {
            if (n % i == 0 && n / i < 1000) {
                return true;
            }
        }
        return false;
    }
}
assylias
  • 321,522
  • 82
  • 660
  • 783