是否有可能合理地模拟收益语法,也许在Java 8的帮助下?

2022-09-04 03:12:49

我今天正在尝试这个问题,来自欧拉问题:

回文数字的读法在两种方式上都是一样的。由两个2位数字的乘积组成的最大回文是9009 = 91 ×99。

找到由两个3位数字的乘积制成的最大回文。

我想过它,它当然可以用for循环来完成,但是我想使用Java 8,因为它开辟了新的选择。

然而,首先,我不知道如何生成一个产生此类元素的函数,所以我最终仍然使用正常的for循环:IntStream

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.OfIntIterator<Integer>IntStreamstreamFromYield.filter(this::isPalindrome).max().getAsInt()

最后,我知道这个问题以前已经问过了,但是上一次已经很久了,现在Java 8很快就会发生,他们已经添加了大概念和新的语言结构,称为lambdas。
因此,现在编写这样的代码可能与人们为Java 6或7编写代码时非常不同。Stream<T>


答案 1

好吧,我认为我们已经从“外部”使用Streams API,使用,优化回文查找算法等。查看蜘蛛鲍里斯阿西利亚斯的答案。但是,我们已经回避了如何使用Python的语句之类的东西编写生成器函数的原始问题。(我认为OP的嵌套 - 例如与使用Python。flatMapyieldyield

使用的问题之一是并行拆分只能发生在最外层的流上。内部流(从 返回)按顺序处理。我们可以尝试使内部流也平行,但它们可能会与外部流竞争。我想嵌套拆分可以工作,但我不太有信心。flatMapflatMap

一种方法是使用或(如assylias的答案)函数。但是,这些会创建无限流,因此必须提供外部流来终止流。Stream.generateStream.iteratelimit

如果我们能创建一个有限但“扁平化”的流,这样整个值流就会被分割,那就太好了。不幸的是,创建流并不像Python的生成器函数那么方便。不过,它可以毫不费力地完成。下面是一个使用 和 类的示例:StreamSupportAbstractSpliterator

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 方法中),我们必须使它们成为对象的字段,并使它们递增,直到迭代完成。现在,这是最简单的分路器形式,它不一定能很好地并行化。通过额外的工作,可以实现该方法以更好地进行拆分,从而实现更好的并行性。tryAdvancetrySplit

该方法可以被重写,它看起来几乎就像嵌套的循环与收益示例,调用 而不是 。不幸的是,它是抽象的,因此必须实现,因此仍然有必要让迭代变量成为对象的字段。forEachRemainingIntConsumeryieldtryAdvance


答案 2

从另一个方向看它怎么样:

您需要 一个 的 ,并且对于该元素的每个元素,您希望该元素中的另一个元素乘以每个 .这是为了:Stream[100,1000)StreamStream[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);
}

不确定得到a然后反过来是否是检测回文的最有效方法,从我的头顶上看,这似乎更快: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;

它给出了相同的答案,但我还没有把它放在严格的测试之下......似乎在我的机器上快了大约100毫秒。

也不确定这个问题是否足够大 - 删除也会给速度带来一点提升。unordered().parallel()

只是试图演示 API 的功能。Stream

编辑

正如@Stuart在注释中指出的那样,由于乘法是可交换的,我们只需要在子流中。这是因为一旦我们检查,我们就不需要检查 。我已经更新了答案。IntStream.range(i, 1000)a x bb x a


推荐