Reputation: 28810
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
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
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
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
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
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