Java 8引入了一个Stream类,类似于Scala的Stream,这是一个功能强大的惰性结构,使用它可以非常简洁地执行这样的操作:
def from(n: Int): Stream[Int] = n #:: from(n+1)
def sieve(s: Stream[Int]): Stream[Int] = {
s.head #:: sieve(s.tail filter (_ % s.head != 0))
}
val primes = sieve(from(2))
primes takeWhile(_ < 1000) print // prints all primes less than 1000
我想知道在Java 8中是否可以这样做,所以我写了这样的东西:
IntStream from(int n) {
return IntStream.iterate(n, m -> m + 1);
}
IntStream sieve(IntStream s) {
int head = s.findFirst().getAsInt();
return IntStream.concat(IntStream.of(head), sieve(s.skip(1).filter(n -> n % head != 0)));
}
IntStream primes = sieve(from(2));
相当简单,但它产生java.lang.IllegalStateException: stream has already been operated upon or closed
,因为findFirst()
和skip()
都是Stream
上的终端操作,只能执行一次。
我实际上不必两次使用流,因为我需要的只是流中的第一个数字,其余的是另一个流,即等同于Scala的Stream.head
和Stream.tail
。 Java 8 Stream
中是否有可用于实现此目的的方法?
感谢。
答案 0 :(得分:10)
即使您没有分割IntStream
的问题,您的代码也无法正常工作,因为您以递归方式调用sieve
方法而不是懒惰。因此,在查询结果流中的第一个值之前,您有一个无穷大递归。
将IntStream s
拆分为头部和尾部IntStream
(尚未消耗)是可能的:
PrimitiveIterator.OfInt it = s.iterator();
int head = it.nextInt();
IntStream tail = IntStream.generate(it::next).filter(i -> i % head != 0);
在这个地方,你需要一个懒惰地在尾巴上调用sieve
的结构。 Stream
没有提供; concat
期望现有流实例作为参数,并且您不能构造一个使用lambda表达式懒惰地调用sieve
的流,因为延迟创建仅使用lambda表达式不支持的可变状态。如果您没有隐藏可变状态的库实现,则必须使用可变对象。但是一旦你接受了可变状态的要求,解决方案就比你的第一种方法更容易:
IntStream primes = from(2).filter(i -> p.test(i)).peek(i -> p = p.and(v -> v % i != 0));
IntPredicate p = x -> true;
IntStream from(int n)
{
return IntStream.iterate(n, m -> m + 1);
}
这将以递归方式创建过滤器,但最终是否创建IntPredicate
s树或IntStream
树(与IntStream.concat
方法一样)无关紧要如果确实有效)。如果您不喜欢过滤器的可变实例字段,则可以将其隐藏在内部类中(但不能隐藏在lambda表达式中......)。
答案 1 :(得分:3)
我的StreamEx库现在有headTail()
个操作来解决问题:
public static StreamEx<Integer> sieve(StreamEx<Integer> input) {
return input.headTail((head, tail) ->
sieve(tail.filter(n -> n % head != 0)).prepend(head));
}
headTail
方法采用BiFunction
,在流终端操作执行期间最多执行一次BiFunction
。所以这个实现是懒惰的:它在遍历开始之前不计算任何东西,并且只计算所请求的素数。 head
接收第一个流元素tail
和其余元素tail
的流,并可以任何方式修改sieve(IntStreamEx.range(2, 1000).boxed()).forEach(System.out::println);
。您可以将它与预定义输入一起使用:
sieve(StreamEx.iterate(2, x -> x+1)).takeWhile(x -> x < 1000)
.forEach(System.out::println);
// Not the primes till 1000, but 1000 first primes
sieve(StreamEx.iterate(2, x -> x+1)).limit(1000).forEach(System.out::println);
但是无限流也可以工作
headTail
还有使用public static StreamEx<Integer> sieve(StreamEx<Integer> input, IntPredicate isPrime) {
return input.headTail((head, tail) -> isPrime.test(head)
? sieve(tail, isPrime.and(n -> n % head != 0)).prepend(head)
: sieve(tail, isPrime));
}
sieve(StreamEx.iterate(2, x -> x+1), i -> true).limit(1000).forEach(System.out::println);
和谓词连接的替代解决方案:
StreamUtils
有趣的是比较递归解决方案:他们能够生成多少素数。
@John McClean解决方案(17793
)
John McClean的解决方案并不是懒惰的:你无法用无限的流来喂它们。所以我只是通过反复试验找到了最大允许上限(public void sieveTest(){
sieve(IntStream.range(2, 17793).boxed()).forEach(System.out::println);
}
)(在发生StackOverflowError之后):
Streamable
@John McClean解决方案(public void sieveTest2(){
sieve(Streamable.range(2, 39990)).forEach(System.out::println);
}
)
39990
增加LazySeq
以上的上限会导致StackOverflowError。
@frhack解决方案(LazySeq<Integer> ints = integers(2);
LazySeq primes = sieve(ints); // sieve method from @frhack answer
primes.forEach(p -> System.out.println(p));
)
53327
结果:在素数= Prime.stream().forEach(System.out::println);
之后卡住了大量的堆分配和垃圾收集占用了90%以上。从53323提升到53327花了几分钟,所以等待更多似乎是不切实际的。
@vidi解决方案
134417
结果:素数= sieve(StreamEx.iterate(2, x -> x+1)).forEach(System.out::println);
后的StackOverflowError。
我的解决方案(StreamEx)
236167
结果:素数= rxjava
后的StackOverflowError。
@frhack解决方案(Observable<Integer> primes = Observable.from(()->primesStream.iterator());
primes.forEach((x) -> System.out.println(x.toString()));
)
367663
结果:素数= IntStream primes=from(2).filter(i->p.test(i)).peek(i->p=p.and(v->v%i!=0));
primes.forEach(System.out::println);
后的StackOverflowError。
@Holger解决方案
368089
结果:素数= sieve(StreamEx.iterate(2, x -> x+1), i -> true).forEach(System.out::println);
后的StackOverflowError。
我的解决方案(带有谓词连接的StreamEx)
368287
结果:素数= @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_action_bar);
getSupportActionBar().setTitle("Set Your Title Here");
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setIcon(R.drawable.youricon);
}
后的StackOverflowError。
因此,涉及谓词连接的三个解决方案获胜,因为每个新条件仅增加了2个堆栈帧。我认为,它们之间的差异是微不足道的,不应该被视为定义胜利者。但是,我更喜欢我的第一个StreamEx解决方案,因为它更类似于Scala代码。
答案 2 :(得分:2)
你基本上可以像这样实现它:
static <T> Tuple2<Optional<T>, Seq<T>> splitAtHead(Stream<T> stream) {
Iterator<T> it = stream.iterator();
return tuple(it.hasNext() ? Optional.of(it.next()) : Optional.empty(), seq(it));
}
在上面的示例中,Tuple2
和Seq
是从jOOλ借来的类型,jOOQ是我们为{{3}}集成测试开发的库。如果您不想要任何其他依赖项,您可以自己实现它们:
class Tuple2<T1, T2> {
final T1 v1;
final T2 v2;
Tuple2(T1 v1, T2 v2) {
this.v1 = v1;
this.v2 = v2;
}
static <T1, T2> Tuple2<T1, T2> tuple(T1 v1, T2 v2) {
return new Tuple<>(v1, v2);
}
}
static <T> Tuple2<Optional<T>, Stream<T>> splitAtHead(Stream<T> stream) {
Iterator<T> it = stream.iterator();
return tuple(
it.hasNext() ? Optional.of(it.next()) : Optional.empty,
StreamSupport.stream(Spliterators.spliteratorUnknownSize(
it, Spliterator.ORDERED
), false)
);
}
答案 3 :(得分:2)
下面的解决方案不会进行状态突变,除了流的头/尾解构。
使用IntStream.iterate获取延迟。 Prime类用于保持发电机状态
import java.util.PrimitiveIterator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Prime {
private final IntStream candidates;
private final int current;
private Prime(int current, IntStream candidates)
{
this.current = current;
this.candidates = candidates;
}
private Prime next()
{
PrimitiveIterator.OfInt it = candidates.filter(n -> n % current != 0).iterator();
int head = it.next();
IntStream tail = IntStream.generate(it::next);
return new Prime(head, tail);
}
public static Stream<Integer> stream() {
IntStream possiblePrimes = IntStream.iterate(3, i -> i + 1);
return Stream.iterate(new Prime(2, possiblePrimes), Prime::next)
.map(p -> p.current);
}
}
用法如下:
Stream<Integer> first10Primes = Prime.stream().limit(10)
答案 4 :(得分:1)
如果你不介意使用第三方库cyclops-streams,我写的库有很多潜在的解决方案。
StreamUtils类有大量静态方法可直接使用java.util.stream.Streams
headAndTail
。
HeadAndTail<Integer> headAndTail = StreamUtils.headAndTail(Stream.of(1,2,3,4));
int head = headAndTail.head(); //1
Stream<Integer> tail = headAndTail.tail(); //Stream[2,3,4]
Streamable类表示可重放的Stream
,并通过构建一个惰性缓存中间数据结构来工作。因为它是缓存和偿还 - 头部和尾部可以直接和单独实现。
Streamable<Integer> replayable= Streamable.fromStream(Stream.of(1,2,3,4));
int head = repayable.head(); //1
Stream<Integer> tail = replayable.tail(); //Stream[2,3,4]
cyclops-streams还提供了一个顺序Stream
扩展,后者又扩展了jOOλ,并且基于Tuple
(来自jOOλ)和域对象(HeadAndTail)解决方案适用于头部和尾部提取。
SequenceM.of(1,2,3,4)
.splitAtHead(); //Tuple[1,SequenceM[2,3,4]
SequenceM.of(1,2,3,4)
.headAndTail();
根据Tagir的请求更新 - &gt;使用SequenceM
public void sieveTest(){
sieve(SequenceM.range(2, 1_000)).forEach(System.out::println);
}
SequenceM<Integer> sieve(SequenceM<Integer> s){
return s.headAndTailOptional().map(ht ->SequenceM.of(ht.head())
.appendStream(sieve(ht.tail().filter(n -> n % ht.head() != 0))))
.orElse(SequenceM.of());
}
另一个版本来自Streamable
public void sieveTest2(){
sieve(Streamable.range(2, 1_000)).forEach(System.out::println);
}
Streamable<Integer> sieve(Streamable<Integer> s){
return s.size()==0? Streamable.of() : Streamable.of(s.head())
.appendStreamable(sieve(s.tail()
.filter(n -> n % s.head() != 0)));
}
注意 - Streamable
的{{1}}都没有空实现 - 因此SequenceM
的大小检查和Streamable
的使用。
最后使用普通headAndTailOptional
java.util.stream.Stream
另一个更新 - 使用对象而不是基元的@ Holger版本的懒惰迭代(注意原始版本也是可能的)
import static com.aol.cyclops.streams.StreamUtils.headAndTailOptional;
public void sieveTest(){
sieve(IntStream.range(2, 1_000).boxed()).forEach(System.out::println);
}
Stream<Integer> sieve(Stream<Integer> s){
return headAndTailOptional(s).map(ht ->Stream.concat(Stream.of(ht.head())
,sieve(ht.tail().filter(n -> n % ht.head() != 0))))
.orElse(Stream.of());
}
答案 5 :(得分:0)
要获得头尾,您需要一个Lazy Stream实现。 Java 8流或RxJava不适合。
您可以使用例如LazySeq,如下所示。
懒惰序列总是从一开始就使用非常便宜的遍历 第一次/休息分解(head()和tail())
LazySeq实现java.util.List接口,因此可以使用 各种各样的地方。此外,它还实现了Java 8增强功能 集合,即流和收集器
package com.company;
import com.nurkiewicz.lazyseq.LazySeq;
public class Main {
public static void main(String[] args) {
LazySeq<Integer> ints = integers(2);
LazySeq primes = sieve(ints);
primes.take(10).forEach(p -> System.out.println(p));
}
private static LazySeq<Integer> sieve(LazySeq<Integer> s) {
return LazySeq.cons(s.head(), () -> sieve(s.filter(x -> x % s.head() != 0)));
}
private static LazySeq<Integer> integers(int from) {
return LazySeq.cons(from, () -> integers(from + 1));
}
}
答案 6 :(得分:0)
这是另一种使用Holger建议的方法。 它使用RxJava来增加使用take(int)方法和其他许多方法的可能性。
package com.company;
import rx.Observable;
import java.util.function.IntPredicate;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
final IntPredicate[] p={(x)->true};
IntStream primesStream=IntStream.iterate(2,n->n+1).filter(i -> p[0].test(i)).peek(i->p[0]=p[0].and(v->v%i!=0) );
Observable primes = Observable.from(()->primesStream.iterator());
primes.take(10).forEach((x) -> System.out.println(x.toString()));
}
}
答案 7 :(得分:0)
这里提供了许多有趣的建议,但是如果有人需要一种不依赖第三方库的解决方案,我想出了这一点:
import java.util.AbstractMap;
import java.util.Optional;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
/**
* Splits a stream in the head element and a tail stream.
* Parallel streams are not supported.
*
* @param stream Stream to split.
* @param <T> Type of the input stream.
* @return A map entry where {@link Map.Entry#getKey()} contains an
* optional with the first element (head) of the original stream
* and {@link Map.Entry#getValue()} the tail of the original stream.
* @throws IllegalArgumentException for parallel streams.
*/
public static <T> Map.Entry<Optional<T>, Stream<T>> headAndTail(final Stream<T> stream) {
if (stream.isParallel()) {
throw new IllegalArgumentException("parallel streams are not supported");
}
final Iterator<T> iterator = stream.iterator();
return new AbstractMap.SimpleImmutableEntry<>(
iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty(),
StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
);
}
答案 8 :(得分:0)
这也适用于并行流:
public static <T> Map.Entry<Optional<T>, Stream<T>> headAndTail(final Stream<T> stream) {
final AtomicReference<Optional<T>> head = new AtomicReference<>(Optional.empty());
final var spliterator = stream.spliterator();
spliterator.tryAdvance(x -> head.set(Optional.of(x)));
return Map.entry(head.get(), StreamSupport.stream(spliterator, stream.isParallel()));
}
答案 9 :(得分:-1)
如果你想获得一个流的头,只需:
IntStream.range(1, 5).first();
如果你想获得一个流的尾部,只需:
IntStream.range(1, 5).skip(1);
如果你想得到一个流的头部和尾部,只需:
IntStream s = IntStream.range(1, 5);
int head = s.head();
IntStream tail = s.tail();
如果你想找到素数,只需:
LongStream.range(2, n)
.filter(i -> LongStream.range(2, (long) Math.sqrt(i) + 1).noneMatch(j -> i % j == 0))
.forEach(N::println);
如果您想了解更多信息,请转到获取AbacusUtil
声明:我是AbacusUtil的开发者。