我最近开始玩java流。 现在我想出了
Stream<T> mapUntil(Stream<T> in, Function<T,T> mapFunc, Predicate<Stream<T>> predicate)
或更一般的
Stream<T> applyUntil(Stream<T> in, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> predicate)
和他们天真的实施
Stream<T> mapUntil(Stream<T> in, Function<T,T> mapFunc, Predicate<Stream<T>> predicate){
return applyUntil(in,in->in.map(mapFunc),predicate)
}
Stream<T> applyUntil(Stream<T> in, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> predicate){
if(predicate.test(in)) return in;
return applyUntil(func.apply(in),func,predicate);
}
可悲的是,mapUntil(stream,mapFunc,s->s.anyMatch(predicate))
会产生IllegalStateException: Stream has already been operated on or closed
,这是合乎逻辑的,因为我在同一个流上调用了anyMatch
和map
。所以我想出了applyUntil
的不同实现:
Stream<T> applyUntil(Stream<T> in, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> predicate){
List<T> collected = in.collect(Collectors.toList());
if(predicate.test(collected.stream())) return collected.stream()
return applyUntil(func.apply(collected.stream(),func,predicate);
}
这显然有很多问题。
collect(Collectors.toList())
我尝试修改我的代码以消除第二个问题,重写applyUntil
:
Stream<T> applyUntil(Stream<T> in, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> predicate){
List<T> collected = in.collect(Collectors.toList());
return applyUntil(()->collected.stream(),func,predicate);
}
Stream<T> applyUntil(Supplier<Stream<T>> sup, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> pred){
if(predicate.test(sup.get())) return sup.get();
return applyUntil(()->func.apply(sup.get()),func,predicate);
}
这个实现确实有效 - 但速度非常慢,特别是如果你有一个非常昂贵的功能。当我仔细观察它时,我意识到原因:它正在调用predicate.test(collected.stream())
,predicate.test(func.apply(collected.stream()))
,predicate.test(func.apply(func.apply(collected.stream()))
等等,导致O(n^2)
调用func
,进行比较到n
所需的电话。那不好。
在我天真的世界里,应该有比这两者更好的解决方案。类似的东西(只是快速草案,AddFirst - Exists是MyStream的简单延迟实现。我在这段代码的末尾错过了像我的Fork这样的类,用于默认的java Streams):
interface MyStream<T>{
T get();
boolean hasNext();
}
class Convert<T> implements MyStream<T>{
Iterator<T> inner;
pulic Convert(Interator<T> iter){
inner=iter;
}
public boolean hasNext(){
return inner.hasNext();
}
public T get(){
return inner.get();
}
class AddFirst<T> implements MyStream<T>{
T item;
MyStream<T> inner;
boolean used;
public AddFirst(T t, MyStream<T> prev){
item=t;
inner=prev;
used=false;
}
public T get(){
if(used) return inner.get();
used=true;
return item;
}
public boolean hasNext(){
return !used || inner.hasNext();
}
}
class Filter<T> implements MyStream<T>{
Predicate<T> filter;
MyStream<T> inner
public Filter(Predicate<T> test, MyStream<T> prev){
filter=test;
inner=prev;
}
public T get(){
while(true){
T curr = inner.get(); //if !inner.hasNext, this throws NoSuchElementException
if(filter.test(curr)) return curr;
}
}
public boolean hasNext(){
try{
T item = get();
inner = new AddFirst(item,inner);
return true;
}
catch(NoSuchElementException e){
return false;
}
}
}
class Map<K,T> implements MyStream<T>{
MyStream<K> inner;
Function<K,T> func;
public Map(Function<K,T> func,MyStream<K> prev){
this.func=func;
inner = prev;
}
public T get(){
return func.apply(inner.get());
}
public boolean hasNext(){
return inner.hasNext();
}
}
class Forall<T> implements Predicate<MyStream<T>>{
Predicate<T> pred;
public Forall(Predicate<T> func){
pred=func;
}
public boolean test(MyStream<T> ms){
while(ms.hasNext()){
if(!pred.test(ms.get()) return false;
}
return true;
}
}
class Exists<T> implements Predicate<MyStream<T>>{
Predicate<T> pred;
public Forall(Predicate<T> func){
pred=func;
}
public boolean test(MyStream<T> ms){
while(ms.hasNext()){
if(pred.test(ms.get()) return true;
}
return false;
}
}
class Fork<T>{
Deque<T> advance;
MyStream<T> inner;
boolean ahead;
MyStream<T> master;
MyStream<T> slave;
public Fork(MyStrem<T> prev){
inner=prev;
advance= new LinkedList<T>();
ahead=false;
master = new ForkStream(true);
slave = new ForkStream(false);
}
public MyStream<T> getMaster(){
return master;
}
public Iterator<T> getMasterIter(){
return master;
}
public MyStream<T> getSlave(){
return slave;
}
public Iterator<T> getSlaveIter(){
return slave;
}
class ForkStream implements MyStream<T>, Iterator<T>{
boolean role;
public ForkStream(boolean in){
role=in;
}
public T get(){
if(role==ahead||advance.size()==0){
ahead=role;
T item = inner.get();
advance.addLast(item);
return item;
}
else{
return advance.removeFirst();
}
}
public boolean hasNext(){
return (role!=ahead&&advance.size()!=0) || inner.hasNext();
}
public T next(){
return get();
}
}
}
通过这些课程,我可以将我的方法重写为:
Stream<T> applyUntil(Stream<T> in, Function<Stream<T>,Stream<T>> func,
Predicate<Stream<T>> predicate){
Fork<T> fork = new Fork(new Convert<T>(in.iterator()));
Stream<T> master = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(fork.getMasterIter(),0),false);
Stream<T> slave = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(fork.getSlaveIter(),0),false);
if(predicate.test(master)) return slave
return applyUntil(func.apply(slave,func,predicate);
}
适用于无限的Streams,它仍然是懒惰的,它重用了计算值。这就像一个适合各种目的的一体化设备。
编辑:当我试图解释为什么最后一个代码块无法编译时,我找到了一种方法来编译它。它仍然不是很好,丢失了许多流魔法,不是Thread-Save等。此外,MyStream应该有一个close
方法,以表示你是否对任何数据不再感兴趣 - 所以Fork
不必为你保存。所以我的脑子里出现了另一个问题:你能从像迭代器这样的东西创建一个`j.u.stream.Stream告诉它的迭代器它不再对任何数据感兴趣(因为短路)吗?
所以我的问题是:没有外部Libary的JDK8是否有像我的Fork这样的东西,让更多的魔法活着? 如果是:哪个类/方法可以帮助我? 如果否:为什么不呢?并且:你怎么能自己实现它,尽可能多地保持魔法?
感谢阅读,抱歉这篇长文:/
亚历
答案 0 :(得分:2)
Java 9将有takeWhile和dropWhile。将这些与Stream.concat结合起来就可以了
Stream.concat(
sourceCollection.stream().takeWhile(predicate).map(mapper),
sourceCollection.stream().dropWhile(predicate.negate())
)
这不会占用中间集合的额外内存,但会通过遍历前缀两次来消耗CPU时间,除非它在到达第二个流之前可以短路。
对于更有效的解决方案,您可以通过提取stream.spliterator()
,将其包装到Spliterator
的自定义子类 - 或j.u.Spliterators.AbstractSpliterator
中来实现中间有状态操作(例如条件映射函数)。你对实现并行性支持很懒惰 - 然后使用j.u.s.StreamSupport.stream(Spliterator<T>, boolean)
将它包装回流中。
答案 1 :(得分:1)
首先,您必须纠正对性能问题的分析。在第二个变体中,调用func.apply
的频率并不重要,因为该函数不执行任何工作。它所做的就是在Stream上链接另一个中间操作,稍后会对其进行处理,但这取决于该处理,它对性能有多大影响。
在这方面,您过分关注大型甚至无限流上的短路操作,这些操作恰好在您的特定设置中很早就完成。基本问题是,您的每个过滤步骤都可能处理所有Stream元素,并且必须在下一个过滤步骤之前完成它,并且存在不可预测数量的过滤步骤,不会发生变化。
如果你总是使用允许早期短路的谓词和流元素的组合,它会让你的第三个解决方案大放异彩,但请注意,在这些情况下,你的第二个解决方案的问题不是嵌套的函数应用程序,而是你在此之前仍然将整个初始流收集到List
。当您跳过该步骤并首先调用接受Supplier<Stream<T>>
的方法时,您不会遇到这些问题。
然后,将元素缓冲到Deque
或不是,根据您链接到Stream的实际中间操作的权重,这是一个小的权衡。请注意,您可以使用Stream API执行第3种方法中的操作,而无需镜像它:
/** returns a {@code List} containing two {@code Stream}s */
public static <T> List<Stream<T>> fork(Stream<T> source) {
Spliterator<T> srcSp=source.spliterator();
ArrayDeque<T> deque=new ArrayDeque<>();
Boolean[] ahead={ null };
final class Branch extends Spliterators.AbstractSpliterator<T> {
private final Boolean type;
Branch(Boolean b) {
super(srcSp.estimateSize(), srcSp.characteristics());
type=b;
}
public boolean tryAdvance(Consumer<? super T> action) {
if(deque.isEmpty() || ahead[0]==type) {
if(!srcSp.tryAdvance(deque::push)) return false;
ahead[0]=type;
action.accept(deque.peek());
return true;
}
action.accept(deque.removeLast());
return true;
}
}
return Arrays.asList(
StreamSupport.stream(new Branch(true), false),
StreamSupport.stream(new Branch(false), false));
}
public static <T> Stream<T> applyUntil(
Stream<T> in, Function<Stream<T>,Stream<T>> func, Predicate<Stream<T>> predicate) {
List<Stream<T>> fork = fork(in);
return predicate.test(fork.get(0))? fork.get(1):
applyUntil(func.apply(fork.get(1)), func, predicate);
}
但是如上所述,它只能帮助您在短路操作的小角落里,除非您有非常昂贵的中间操作,否则它不会比您重新应用第二种方法更快,如果您消除了整个初始收集流式传输到List
。