Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure - Make first + filter lazy

I am learning clojure. While solving one of the problem, I had to use first + filter. I noted that the filter is running unnecessarily for all the inputs. How can I make the filter to run lazily so that it need not apply the predicate for the whole input.

The below is an example showing that it is not lazy,

(defn filter-even
  [n]
  (println n)
  (= (mod n 2) 0))

(first (filter filter-even (range 1 4)))

The above code prints

1

2

3

Whereas it need not go beyond 2. How can we make it lazy?

like image 341
Kannan Ramamoorthy Avatar asked Dec 24 '22 04:12

Kannan Ramamoorthy


1 Answers

This happens because range is a chunked sequence:

(chunked-seq? (range 1))
=> true

And it will actually take the first 32 elements if available:

(first (filter filter-even (range 1 100)))
1
2
. . .
30
31
32
=> 2

This overview shows an unchunk function that prevents this from happening. Unfortunately, it isn't standard:

(defn unchunk [s]
  (when (seq s)
    (lazy-seq
      (cons (first s)
            (unchunk (next s))))))

(first (filter filter-even (unchunk (range 1 100))))
2
=> 2

Or, you could apply list to it since lists aren't chunked:

(first (filter filter-even (apply list (range 1 100))))
2
=> 2

But then obviously, the entire collection needs to be realized pre-filtering.

This honestly isn't something that I've ever been too concerned about though. The filtering function usually isn't too expensive, and 32 element chunks aren't that big in the grand scheme of things.

like image 79
Carcigenicate Avatar answered Jan 01 '23 20:01

Carcigenicate