【问题标题】:Java 8: streams and the Sieve of EratosthenesJava 8:流和埃拉托色尼筛
【发布时间】:2017-10-01 07:41:17
【问题描述】:

埃拉托色尼筛法可以在 Haskell 中非常巧妙地实现,利用惰性生成一个无限列表,然后从其尾部删除列表头部的所有倍数:

primes :: [Int]
primes = sieve [2..]
sieve (x:xs) = x : sieve [y | y <- xs, y `mod` x > 0]

我正在尝试学习如何在 Java 8 中使用流,但我想知道是否有一种方法可以在 Java 中实现与上面的 Haskell 代码相同的结果。如果我将 Haskell 惰性列表视为等同于 Java 流,似乎我需要获取一个以 2 为首的流并生成一个删除所有 2 倍数的新流,然后获取该流并生成一个包含所有移除了 3 的倍数,并且...

我不知道如何继续。

有没有办法做到这一点,或者当我试图将 Java 流视为与 Haskell 列表相当时,我是在自欺欺人吗?

【问题讨论】:

  • “当我试图将 Java 流与 Haskell 列表相媲美时,我是在自欺欺人吗” - 我会说是的。 也许你可以做类似的事情,但在 Java 中做起来肯定会更复杂。
  • 使用过 C# (LINQ) 和 Haskell,我只能说让 Java 的流来做类似的事情并不简单。我建议更接近惯用的 Java。恕我直言,Java 缺乏清晰简洁地表达这些复杂想法的内置插件。
  • @Holger 是的,“stream”对于那些来自“java”标签的人来说是多余的,但是对于那些来自“haskell”甚至只是“筛子-of-eratosthenes”的人来说呢? “更有意义?将“流”作为附加标签有什么害处?
  • @Holger 因为惰性列表是流?

标签: java haskell java-stream primes sieve-of-eratosthenes


【解决方案1】:

当然,这是可能的,但由于 Java 流没有简单的方法将其分解为它们的头部和尾部这一事实而变得非常复杂(您可以轻松获得其中任何一个,但不能同时获得,因为流已经拥有到那时已经使用 - 听起来有人可以使用线性类型...)。

解决方案是保留一个可变变量。例如,该可变变量可以是谓词,用于测试一个数字是否是迄今为止看到的任何其他数字的倍数。

import java.util.stream.*;
import java.util.function.IntPredicate;

public class Primes {

   static IntPredicate isPrime = x -> true;
   static IntStream primes = IntStream
                               .iterate(2, i -> i + 1)
                               .filter(i -> isPrime.test(i))
                               .peek(i -> isPrime = isPrime.and(v -> v % i != 0));

   public static void main(String[] args) {
      // Print out the first 10 primes.
      primes.limit(10)
            .forEach(p -> System.out.println(p));

   }
}

然后,你会得到预期的结果:

$ javac Primes.java
$ java Primes
2
3
5
7
11
13
17
19
23
29

【讨论】:

  • 接近this one,不过,我更喜欢像shown here 那样实现真正的埃拉托色尼筛...
  • @Holger:好的,但这需要对要生成的素数数量进行定义限制。 Haskell 解决方案处理一个无限列表。
  • 谢谢!我可以看到,在流自动成为我的 Java 词汇表的一部分之前,我还有很长的路要走——但我应该预料到,OOP 花了大约 2 年的时间,而那是 20 多年前的事了——我的现在的脑细胞并不比以前更活泼!
  • @user1636349:这是实际的埃拉托色尼筛算法的内在限制。另一方面,这个 Stream 解决方案将在后台构建一个链接的Predicate 链,这会占用更多的内存,尽管性能下降如此之大,以至于它似乎在到达OutOfMemoryError 之前就挂起。您可以在没有limit(…) 的情况下运行此答案的解决方案,以查看它何时会挂起或抛出StackOverflowErrorOutOfMemoryError。然后取它打印的最后一个素数乘以 10,将其传递给我的基于 BitSet 的解决方案并观察差异......
  • @Holger 也许可以通过postponement(也可以是thisthis)来扩充这个算法,以实现显着的加速。
【解决方案2】:

如果您愿意接受 Scala 解决方案,这里是:

def sieve(nums:Stream[Int]):Stream[Int] = nums.head #:: sieve(nums.filter{_ % nums.head > 0})
val primes:Stream[Int] = sieve(Stream.from(2))

它不像 Haskell 解决方案那样优雅,但它非常接近 IMO。 这是输出:

scala> primes take 10 foreach println
2
3
5
7
11
13
17
19
23
29

Scala 的 Stream 是一个惰性列表,它比 Java 8 Stream 惰性得多。在文档中,您甚至可以找到 对应于canonical Haskell zipWith implementation 的斐波那契数列实现示例。

【讨论】:

  • 谢谢。我想要一个 Java 解决方案,而且我只玩过 Scala,但我会考虑这个...... :)
【解决方案3】:

编辑:筛子,未优化,返回无限的素数流

public static Stream<Integer> primeStreamEra() {
    final HashMap<Integer, Integer> seedsFactors =
        new HashMap<Integer, Integer>();
    return IntStream.iterate(1, i -> i + 1)
                    .filter(i -> {
                        final int currentNum = i;
                        seedsFactors.entrySet().parallelStream()
                            .forEach(e -> {
                                // Update all factors until they have
                                //the closest value that is >= currentNum
                                while(e.getValue() < currentNum)
                                    e.setValue(e.getValue() + e.getKey());
                            });
                        if(!seedsFactors.containsValue(i)) {
                            if(i != 1)
                                seedsFactors.put(i, i);
                            return true;
                        }
                        return false;
                    }).boxed();
}

测试:

public static void main(String[] args) {
    primeStreamEra().forEach(i -> System.out.println(i));
}

初始帖子:

一种更简单的解决方案,避免了一些不必要的操作(例如测试偶数)。

我们从 3 到极限迭代所有奇数。

过滤功能内:

  • 我们测试我们发现小于/等于 sqrt(currentNumber) 向下舍入的所有素数。
  • 如果它们除以我们当前的数字,则返回 false
  • 否则添加到找到的素数列表并返回true

功能:

public static IntStream primeStream(final int limit) {
    final ArrayList<Integer> primes = new ArrayList<Integer>();
    IntStream primesThreeToLimit =  
           IntStream.iterate(3, i -> i + 2)
                    .takeWhile(i -> i <= limit)
                    .filter(i -> {
                        final int testUntil = (int) Math.sqrt((double) limit);
                        for(Integer p: primes) {
                            if(i % p == 0) return false;
                            if(p > testUntil) break;
                        }
                        primes.add(i);
                        return true;
                    });
    return IntStream.concat(IntStream.of(1,2), primesThreeToLimit);
}

测试:

public static void main(String[] args) {
    System.out.println(Arrays.toString(primeStream(50).toArray()));
}

输出:[1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

编辑:要从IntStream 转换为Stream&lt;Integer&gt;,只需执行primeStream(50).boxed()

【讨论】:

  • 我意识到这是一个比 OP 更优化的试除法,但它仍然是试除法而不是筛子。
  • @DanaJ 你是对的。我在答案中添加了实际筛子的流实现。
【解决方案4】:

另一种解决方案,您可以实现 Collector 接口。

  public static void main(String[] args)
  {
    Collector<Integer, List<Integer>, List<Integer>> sieve = new Collector<Integer, List<Integer>, List<Integer>>()
    {
      @Override
      public Supplier<List<Integer>> supplier()
      {
        return ArrayList::new;
      }

      @Override
      public BiConsumer<List<Integer>, Integer> accumulator()
      {
        return (prevPrimes, candidate) ->
        {
          if (prevPrimes.stream().noneMatch(p -> candidate % p == 0))
          {
            prevPrimes.add(candidate);
          }
        };
      }

      @Override
      public BinaryOperator<List<Integer>> combiner()
      {
        return (list1, list2) ->
        {
          list1.addAll(list2);
          return list1;
        };
      }

      @Override
      public Function<List<Integer>, List<Integer>> finisher()
      {
        return Function.identity();
      }

      @Override
      public Set<Characteristics> characteristics()
      {
        Set<Characteristics> set = new HashSet<>();
        set.add(Characteristics.IDENTITY_FINISH);
        return set;
      }
    };

    List<Integer> primesBelow1000 = IntStream.range(2, 1000)
        .boxed()
        .collect(sieve);

    primesBelow1000.forEach(System.out::println);
  }

更简洁:

  public static void main(String[] args)
  {

    List<Integer> primesBelow1000 = IntStream.range(2, 1000)
        .boxed()
        .collect(
            ArrayList::new,
            (primes, candidate) ->
            {
              if (primes.stream().noneMatch(p -> candidate % p == 0))
              {
                primes.add(candidate);
              }
            },
            List::addAll
        );

    primesBelow1000.forEach(System.out::println);
  }

更高效(使用 Java 9 TakeWhile 将 O(n) 更改为 O(sqrt(n))):

List<Long> primesBelowLimit = LongStream.range(2, below)
        .collect(
                ArrayList::new,
                (primes, candidate) ->
                {
                    long candidateRoot = (long) Math.sqrt(candidate);
                    if (primes.stream()
                        .takeWhile(p -> p <= candidateRoot)
                        .noneMatch(p -> candidate % p == 0))
                    {
                        primes.add(candidate);
                    }
                },
                List::addAll
        );

【讨论】:

  • 对于 Java 9,我们可以只使用 filter 吗?在 Java 8 上获得相同的效率。顺便说一句,答案很好
【解决方案5】:

可能是这样的解决方案?

public class ErythropheneSieveFunctionBitSet implements IntFunction<BitSet> {

    @Override
    public BitSet apply(int value) {
        BitSet set = new BitSet();
        fillSet(set, value);

        int s = set.stream().min().getAsInt();
        while (s * s <= value) {
            int temp = s;
            int i = 0;
            int multipleTemp;
            while ((multipleTemp = s * (s + i)) <= value) {
                set.clear(multipleTemp);
                i++;
            }
            s = set.stream().filter(x -> x > temp).min().getAsInt();
        }

        return set;
    }

    private void fillSet(BitSet set, int value) {
        for (int i = 2; i < value; i++) {
            set.set(i);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2018-07-21
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多