我今天正在尝试这个问题,来自欧拉问题:
回文数字两种方式相同。由两个2位数字的乘积制成的最大回文是9009 = 91×99。
找出由两个3位数字的乘积制成的最大回文。
我考虑过它,它当然可以用for循环完成,但是我想使用Java 8,因为它打开了新选项。
然而,首先,我不知道如何生成产生这些元素的IntStream
,所以我仍然使用正常的for循环:
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";
}
}
正如你所看到的,我可能有点懒,真的IntStream::max
是一个非常好的方法,我觉得最好用它来自己编写。
虽然问题出现了,我需要让list
现在能够以这种方式获得最大值,这意味着我需要存储数据,我应该在哪里不这样做。
那么,现在的问题是,是否可以在Java 8中实现它?
for (int i = min; i < max; i++) {
for (int j = min; j < max; j++) {
yield i * j;
}
}
然后从该方法中创建PrimitiveIterator.OfInt
(Iterator<Integer>
的取消装箱版本,或直接创建IntStream
?
然后使用streamFromYield.filter(this::isPalindrome).max().getAsInt()
获得答案将非常容易实现。
最后,我知道之前已经提出过这个问题,但是最后一次已经很久了,现在Java 8将很快发生,他们已经添加了大概念Stream<T>
和新的语言结构,称为lambdas
因此,现在制作这样的代码可能与人们为Java 6或7制作代码时的情况大不相同。
答案 0 :(得分:6)
好吧,我认为我们已经使用来自“外部”的Streams API,使用flatMap
,优化回文查找算法,等等。请参阅Boris the Spider和{{的答案3}}。但是,我们回避了如何使用Python的yield
语句编写生成器函数的原始问题。 (我认为OP的嵌套 - 例如yield
使用的是Python。)
使用flatMap
的一个问题是并行拆分只能发生在最外层的流上。内部流(从flatMap
返回)按顺序处理。我们可以尝试使内部流也平行,但它们可能与外部流竞争。我认为嵌套拆分可以工作,但我不太自信。
一种方法是使用Stream.generate
或(如assylias'答案)Stream.iterate
函数。但是,这些会创建无限的流,因此必须提供外部limit
来终止流。
如果我们能够创建一个有限但“扁平”的流,那么整个值的流可能会分裂,这将是很好的。不幸的是,创建一个流并不像Python的生成器函数那么方便。不过,它可以毫不费力地完成。以下是使用StreamSupport
和AbstractSpliterator
类的示例:
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());
}
不是让迭代变量在堆栈上(如在带有yield方法的嵌套for中),我们必须使它们成为对象的字段并使tryAdvance
递增它们直到迭代完成。现在,这是分离器的最简单形式,并不一定能很好地并行化。通过额外的工作,可以实现trySplit
方法以更好地进行拆分,从而实现更好的并行性。
可以覆盖forEachRemaining
方法,它看起来几乎就像嵌套for-loop-with-yield示例,调用IntConsumer
而不是yield
。遗憾的是tryAdvance
是抽象的,因此必须实现,所以仍然需要让迭代变量成为对象的字段。
答案 1 :(得分:4)
如何从另一个方向看它:
您需要Stream
[100,1000)
,并且对于Stream
的每个元素,您希望该元素的另一个Stream
乘以[100, 1000)
。这是flatMap
的用途:
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);
}
不确定是否获得String
然后相反是检测回文的最有效方法,从我的头顶看这似乎更快:
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;
它给出了相同的答案,但我没有对它进行严格的测试......似乎在我的机器上快了大约100ms。
同样不确定这个问题对于unordered().parallel()
来说是否足够大 - 删除它会对速度产生一点提升。
只是试图展示Stream
API的功能。
修改强>
正如@Stuart在评论中指出的那样,乘法是可交换的,我们只需要在子流中IntStream.range(i, 1000)
。这是因为一旦我们检查a x b
,我们就不需要检查b x a
。我已经更新了答案。
答案 2 :(得分:2)
我会试一试。带循环的版本,然后是流。虽然我从另一端开始,所以它更容易,因为我可以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;
}
}
答案 3 :(得分:2)
总有一些方法可以模拟高估的yield
功能,即使没有Java 8.基本上它是关于存储执行的状态,即堆栈帧,这可以通过一个线程来完成。一个非常简单的实现可能如下所示:
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;
}
}
一旦有了这样的帮助类,用例就会直截了当:
// 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);
以前的代码没有使用任何Java 8功能。但它允许使用它们而无需任何改变:
// the Java 8 way
StreamSupport.stream(y.spliterator(), false).filter(i->isPalindrome(i))
.max(Integer::compare).ifPresent(System.out::println);
请注意,上面的Yield
支持类不是最有效的实现,如果迭代未完成但放弃了Iterator
,它就无法处理。但它表明这种逻辑确实可以在Java中实现(而其他答案令人信服地表明,这样的yield
逻辑不是解决这种问题所必需的。)