dagda1
dagda1

Reputation: 28810

clojure - take-while to include last item when predicate is false

I have the following function that uses take-while

(defn process [[cash amount wrappers]]
  (let [bought (int (Math/floor (/ cash amount)))
        free (->>
              (iterate (partial unwrapper wrappers) bought)
              (take-while (partial (fn [w a]
                                     (prn (str "a = " a))
                                     (>= a w)
                                     ) wrappers)))]

The problem I have is that I want to include the last item when the predicate is false but take-while does not return that last item.

Is there a way round that with take-while or should I use something else?

Upvotes: 6

Views: 1134

Answers (5)

Caleb Macdonald Black
Caleb Macdonald Black

Reputation: 1572

Here is the transducer version:

(defn take-while+
  ([pred]
   (fn [rf]
     (fn
       ([] (rf))
       ([result] (rf result))
       ([result input]
        (if (pred input)
          (rf result input)
          (reduced (conj! result input))))))))

Upvotes: 0

Diego Basch
Diego Basch

Reputation: 13069

You could do something like this, based on the source of take-while:

(defn take-while+
  [pred coll]
  (lazy-seq
    (when-let [[f & r] (seq coll)]
      (if (pred f)
        (cons f (take-while+ pred r))
        [f]))))

Upvotes: 5

Magos
Magos

Reputation: 3014

Had a bit of a try at that partition-by approach I mentioned in my comment to Leon Grapenthin's answer. It generally works out OK, but it turns out when you consume one of the partitions made by it, partition-by eagerly evaluates the next partition. So while this should be a lazy approach, it's less lazy than his solution and consequently can't handle the edge case of an infinite sequence where the predicate mapping looks like (true true ... true false false....) Still, fun problem to experiment on.

(defn take-while-plus-n 
  "Lazily returns successive items from coll while (pred item) returns true,
  then an additional n items. pred must partition coll into segments of finite length."
  [pred n coll]
  (if (pred (first coll))
    (let[[head & tails] (partition-by pred coll)]
      (lazy-cat head (->> tails flatten (take n))))
    (take n coll)))

I threw the variable number of "additional items" on mainly because I ended up using take in both cases.

Upvotes: 3

Alan Thompson
Alan Thompson

Reputation: 29958

I would first create a vector of the predicate results and then process that as desired:

(def xx (range 10))
;=> (0 1 2 3 4 5 6 7 8 9)
(defn my-tst [arg]
  (< arg 5))
(def flags (mapv my-tst xx))
;=> [true true true true true false false false false false]
(def num-true (count (filter identity flags)))
num-true ;=> 5
(def num-keep (inc num-true))
num-keep ;=> 6
(def keepers (take num-keep xx))
keepers 
;=> (0 1 2 3 4 5)

Upvotes: 0

Leon Grapenthin
Leon Grapenthin

Reputation: 9266

The common approach is to use split-with, which returns the results of both take-while and drop-while in a vector. You can then append the first element of the drop-while result.

It requires two passes, though. You might want to write a custom take-while...

(defn take-while-and-one
  [pred coll]
  (lazy-seq
   (when-let [s (seq coll)]
     (if (pred (first s))
       (cons (first s) (take-while-and-one pred (rest s)))
       (list (first s))))))

Upvotes: 3

Related Questions