过滤功能不懒惰

Posted

技术标签:

【中文标题】过滤功能不懒惰【英文标题】:Filter Function Not Lazy 【发布时间】:2018-11-19 03:33:30 【问题描述】:

为了好玩,我正在制作我自己的 Java 流库版本。这是我的班级签名:

class Stream<T> 
  Supplier<T> head;
  Supplier<Stream<T>> tail;
  ...

另外,我写了一个基本的无限流迭代器,它会根据给定的函数生成一个无限列表:

  public static <T> Stream<T> iterate(T first, Function<T, T> f) 
    return new Stream<T>(
            () -> first,
            () -> 
              T nextElem = f.apply(first);
              if (nextElem == null) 
                return generate(() -> null);
               else 
                return iterate(nextElem, f);
              
            
    );
  

函数generate 是迭代的一个特例,它永远重复给定元素。在上面的函数中,我生成了一个无限序列null 来指示流的结束(我认为我不会在流中存储空值)。

然后我写了一个 reduce 函数,它的第二个参数是惰性的:

  public <U> U reduce(U acc, Function<T, Function<Supplier<U>, U>> f) 
    System.out.println("REDUCE CALL");
    T elem = head.get();
    if (elem != null) 
      return f.apply(elem).apply(() -> this.tail.get().reduce(acc, f));
     else 
      return acc;
    
  

在reduce函数的基础上,我编写了filter函数。

  public Stream<T> filter(Predicate<T> p) 
    System.out.println("FILTER");
    return reduce(generate(() -> null), elem -> acc -> 
      if (p.test(elem)) 
        return new Stream<>(
                () -> elem,
                () -> acc.get()
        );
       else 
        return acc.get();
      
    );
  

最后,我开始使用我自己的 Stream 类:

  public static void main(String[] args) 
    Stream<Integer> ilist =
            Stream
              .iterate(1, x -> x + 1)
              .filter(x -> x >= 5);
  

但过滤器并不懒惰!从下面给出的输出中,我认为过滤器会评估元素,直到找到与给定谓词匹配的元素。

FILTER
REDUCE CALL
REDUCE CALL
REDUCE CALL
REDUCE CALL
REDUCE CALL

我的代码出了什么问题,我怎样才能让我的过滤器函数再次变得惰性?

更新:根据 Sweeper 的说法,我在不使用 reduce 的情况下又尝试了 filter 功能。

  public Stream<T> filter2(Predicate<T> p) 
    System.out.println("FILTER2");
    T elem = head.get();
    if (elem == null) 
      return generate(() -> null);
     else 
      if (p.test(elem)) 
        return new Stream<>(
                () -> elem,
                () -> this.tail.get().filter2(p)
        );
       else 
        return this.tail.get().filter2(p);
      
    
  

然而,这个函数也不是惰性的。我使用filter2的main函数的输出如下:

FILTER2
FILTER2
FILTER2
FILTER2
FILTER2

我该如何解决这个问题,有没有办法通过惰性减少来实现惰性过滤器?

致谢:本练习和上述函数的实现受到 Chiusano 和 Bjarnason 的Scala 中的函数式编程一书的启发。

【问题讨论】:

filter 并不懒惰,因为 reduce 并不懒惰。我不认为你可以用非懒惰的reduce 来实现懒惰的filter @Sweeper 有没有办法让 reduce 变得懒惰? 完全不使用reduce会怎样? @Sweeper 我试过了。请参阅帖子以获取更新。 【参考方案1】:

在你写的没有reduce的版本中,元素存在但不满足谓词的情况不是惰性的。您无需像在其他情况下那样将递归调用包装在供应商 lambda 中,而是急切地获取尾部并立即对其进行过滤。

public Stream<T> filter2(Predicate<T> p) 
    System.out.println("FILTER2");
    T elem = head.get();
    if (elem == null) 
        return generate(() -> null);
     else 
        if (p.test(elem)) 
            return new Stream<>(
                () -> elem,
                () -> this.tail.get().filter2(p)
            );
         else 
            return this.tail.get().filter2(p); // <- not lazy!
        
    

您需要的是一种创建流的方法,以便推迟决定它是否为空。

public class Stream<T> 
    // private constructor(s)

    public static <T> Stream<T> empty()  /* ... */ 

    public static <T> Stream<T> cons(Supplier<T> head, Supplier<Stream<T> tail)  /* ... */ 

    public static <T> Stream<T> lazy(Supplier<Stream<T>> stream)  /* ... */ 

    public Stream<T> filter(Predicate<T> p) 
        if ( /* this stream is empty */ ) 
            return Stream.empty();
         else if ( /* head element satisfies predicate */ ) 
            // lazily filter tail, cons head element
         else 
            return Stream.lazy(() -> this.tail.get().filter(p));
        
    

类似的东西。

【讨论】:

我明白了。你会如何让它变得懒惰? 回到绘图板,找出一种不同的设计,让您以这样一种方式创建一个流,您可以推迟决定它是否为空。 我阅读的 Scala 代码更容易理解(使用类似 Lisp 的 Cons)。当然,我会去尝试不同的设计! 我建议提供三种创建流的基本方法(无论是构造函数还是静态工厂方法):一种不带参数并返回空流,一种采用头部和尾部并返回非-empty 流,另一个接受 Supplier&lt;Stream&lt;T&gt;&gt; 并返回可能为空的流,为了懒惰而推迟该决定。 我明白前两个工厂方法的用途,但是第三个工厂方法是干什么用的呢?我对这句话感到困惑:“另一个接受 Supplier&lt;Stream&lt;T&gt;&gt; 并返回可能为空的流。”

以上是关于过滤功能不懒惰的主要内容,如果未能解决你的问题,请参考以下文章

如何使用角度6的分页,过滤,排序功能在表组件中加载大数据

学习打卡功能上线,告别懒惰,坚持学习!

协同过滤-显式评级和隐式评级

懒惰地导入 UI 模块/组件的最佳方法是啥

为啥这个(Django)“过滤器”功能不起作用?

过滤器功能不起作用并给出错误