newbie question

11 views
Skip to first unread message

Warren Wood

unread,
Nov 6, 2009, 12:27:24 PM11/6/09
to Clojure
How to partition a seq, starting a new group every time pred is true?

For example

(my-partition odd? [1 2 3 4 5 6])
-->
((1 2) (3 4) (5 6))

Chouser

unread,
Nov 6, 2009, 12:57:36 PM11/6/09
to clo...@googlegroups.com

There's a partition-by in clojure.contrib.seq-utils, but I don't
see how to use it to build what you want. Here's what I came up
with:

(defn partition-when [f s]
(->> (iterate rest s)
(take-while seq)
(filter (comp f first))
(map (fn [[x & xs]] (cons x (take-while (complement f) xs))))))

user=> (partition-when odd? [1 2 3 4 6 8 9 11 13 14 15])
((1 2) (3 4 6 8) (9) (11) (13 14) (15))

I imagine someone can come up with something more elegant. Also,
this drops everything at the front of the seq until the predicate
matches, which may or may not be when you want:

user=> (partition-when even? [1 2 3 4 6 8 9 11 13 14 15])
((2 3) (4) (6) (8 9 11 13) (14 15))

--Chouser

Warren Wood

unread,
Nov 6, 2009, 1:01:50 PM11/6/09
to Clojure
In the meantime, I came up with the following, which seems to work.
I'm sure it can be improved.

(defn NOT [pred] (fn [x] (not (pred x))))

(defn partition-at-starts [pred coll]
(if (empty? coll)
()
(let [[first-group others] (split-with (NOT pred) (rest coll))]
(lazy-seq (cons (cons (first coll) first-group) (partition-at-
starts pred others))))))

Which leads me to another question, are there standard functions
sitting around somewhere already to do boolean combinations of
predicates (conjoin, disjoin, negate perhaps?)? Like my NOT above.
Perhaps negate would be a better name. To me, having these
significantly improve readability over having an in-line anonymous
function to combine the predicates.

On Nov 6, 11:57 am, Chouser <chou...@gmail.com> wrote:

John Harrop

unread,
Nov 6, 2009, 1:07:26 PM11/6/09
to clo...@googlegroups.com
On Fri, Nov 6, 2009 at 1:01 PM, Warren Wood <warrenth...@yahoo.com> wrote:
In the meantime, I came up with the following, which seems to work.
I'm sure it can be improved.

(defn NOT [pred] (fn [x] (not (pred x))))

... 
Which leads me to another question, are there standard functions
sitting around somewhere already to do boolean combinations of
predicates (conjoin, disjoin, negate perhaps?)?  Like my NOT above.

How about complement?

user=> (def x (complement even?))
#'user/x
user=> (x 4)
false
user=> (x 3)
true

John Harrop

unread,
Nov 6, 2009, 1:10:24 PM11/6/09
to clo...@googlegroups.com
And WHAT the devil is with complement's messy implementation? (defn complement [f] (comp not f)) seems much cleaner. :) 

Garth Sheldon-Coulson

unread,
Nov 6, 2009, 1:29:39 PM11/6/09
to clo...@googlegroups.com
On Fri, Nov 6, 2009 at 12:57 PM, Chouser <cho...@gmail.com> wrote:


I imagine someone can come up with something more elegant.


I'd be interested in seeing something more elegant. I for one can't think of anything good.

John Harrop

unread,
Nov 6, 2009, 1:59:27 PM11/6/09
to clo...@googlegroups.com
(defn partition-at [pred coll]
  (apply conj
    (last
      (take-while (comp first second)
        (rest
          (iterate
            (fn [[res coll]]
              (let [[x y] (split-with (complement pred) (rest coll))]
                [(conj res (cons (first coll) x)) y]))
            [[] coll]))))))

user=> (partition-at odd? [2 1 4 8 6 3 9 10])
[(2) (1 4 8 6) (3) (9 10)]

No loop/recur, no lazy-seq, but it's not lazy and it's still a little hairy. :)

Chouser

unread,
Nov 6, 2009, 3:39:46 PM11/6/09
to clo...@googlegroups.com
On Fri, Nov 6, 2009 at 1:29 PM, Garth Sheldon-Coulson <ga...@mit.edu> wrote:
>

(defn partition-when [f s]
(loop [p [[]], [x & xs :as s] s]
(cond (empty? s) p
(f x) (recur (conj p [x]) xs)
:else (recur (conj (pop p) (conj (peek p) x)) xs))))

Maybe a bit simpler ...or maybe not. Not lazy at all. Behaves
a little differently than my previous:

user=> (partition-when odd? [1 2 3 4 6 8 9 11 13 14 15])

[[] [1 2] [3 4 6 8] [9] [11] [13 14] [15]]

user=> (partition-when even? [1 2 3 4 6 8 9 11 13 14 15])

[[1] [2 3] [4] [6] [8 9 11 13] [14 15]]

Note that the first vector never starts with an item that matches
the predicate -- it's a little bin in which all non-matching
items are dumped first.

--Chouser

Mark Triggs

unread,
Nov 6, 2009, 6:19:28 PM11/6/09
to clo...@googlegroups.com
Joining in the fun!

(defn partition-when [pred coll]
(when (seq coll)
(lazy-seq
(let [[x xs] (split-at (or (some (fn [[i elt]]
(and (pos? i)
(pred elt)
i))
(indexed coll))
(count coll))
coll)]
(cons x (partition-when pred xs))))))


(partition-when odd? [])
=> nil

(partition-when odd? [1 2 3 4 6 8 9 11 13 14 15])

=> ((1 2) (3 4 6 8) (9) (11) (13 14) (15))

(partition-when even? [1 2 3 4 6 8 9 11 13 14 15])

=> ((1) (2 3) (4) (6) (8 9 11 13) (14 15))

;; Laziness
(take 10 (my-partition odd? (iterate inc 1)))
=> ((1 2) (3 4) (5 6) (7 8) (9 10) (11 12) (13 14) (15 16) (17 18) (19 20))


Cheers,

Mark


Chouser <cho...@gmail.com> writes:

--
Mark Triggs
<mark.h...@gmail.com>

Alex Osborne

unread,
Nov 6, 2009, 6:59:00 PM11/6/09
to clo...@googlegroups.com
Mark Triggs wrote:
> Joining in the fun!

Like Mark's but using split-with instead of split-at:

(defn partition-when [pred coll]
(lazy-seq
(when-let [[x & xs] (seq coll)]
(let [[xs ys] (split-with (complement pred) xs)]
(cons (cons x xs) (partition-when pred ys))))))

> (partition-when odd? [])
> => nil
>
> (partition-when odd? [1 2 3 4 6 8 9 11 13 14 15])
> => ((1 2) (3 4 6 8) (9) (11) (13 14) (15))
>
> (partition-when even? [1 2 3 4 6 8 9 11 13 14 15])
> => ((1) (2 3) (4) (6) (8 9 11 13) (14 15))
>
> ;; Laziness

> (take 10 (partition-when odd? (iterate inc 1)))

Mark Triggs

unread,
Nov 6, 2009, 7:11:39 PM11/6/09
to clo...@googlegroups.com
Alex Osborne <a...@meshy.org> writes:

> Like Mark's but using split-with instead of split-at:
>
> (defn partition-when [pred coll]
> (lazy-seq
> (when-let [[x & xs] (seq coll)]
> (let [[xs ys] (split-with (complement pred) xs)]
> (cons (cons x xs) (partition-when pred ys))))))


Lovely! Much better.
--
Mark Triggs
<mark.h...@gmail.com>

ataggart

unread,
Nov 6, 2009, 7:40:27 PM11/6/09
to Clojure
My attempt, since I don't like how split-at has to traverse the input
sequence twice:

(defn take-while-and-next
"Similar to take-while except it returns a vector whose first
element is a
vector containing the taken items, and whose second element is the
remaining
sequence not taken. The optional in parameter is a vector to which
the taken
elements will be conjoined."
([pred coll] (take-while-and-next pred coll []))
([pred coll in]
(if-let [s (seq coll)]
(loop [v s c in]
(if (pred (first v))
(recur (next v) (conj c (first v)))
(vector c v)))
[in nil])))

(defn partition-when
"Partitions coll into a lazy sequence of vectors, starting a new
vector when
(pred item) returns true."
[pred coll]
(lazy-seq
(if-let [s (seq coll)]
(let [e (first s)
[v more] (take-while-and-next (complement pred) (next s)
[e])]
(cons v (partition-when pred more))))))

user=> (partition-when odd? [1 2 3 4 5 6 7])
([1 2] [3 4] [5 6] [7])

user=> (partition-when odd? [2 3 4 5 6 7])
([2] [3 4] [5 6] [7])

user=> (partition-when odd? [1 3 5 6 7])
([1] [3] [5 6] [7])

ataggart

unread,
Nov 6, 2009, 7:44:17 PM11/6/09
to Clojure
And I really hate how google groups choses to do line-wrapping. I
made sure I was within 80 chars ffs.

Alex Osborne

unread,
Nov 6, 2009, 7:47:15 PM11/6/09
to clo...@googlegroups.com
Alex Osborne wrote:
> Like Mark's but using split-with instead of split-at:
>
> (defn partition-when [pred coll]
> (lazy-seq
> (when-let [[x & xs] (seq coll)]
> (let [[xs ys] (split-with (complement pred) xs)]
> (cons (cons x xs) (partition-when pred ys))))))

Just realised this is almost the same as Warren's -- I had missed
reading Warren's before posting. Thought it might be helpful if I
explain the differences:

* lazy-seq on the outside of the conditional. This means the seq is
fully lazy, so if you never call first/next on it, nothing is ever run.

* As suggested by others complement instead of NOT.

* (when (seq coll) ...) instead of (if (empty? coll) () ...). We can
simplify things like this as (lazy-seq nil) => () and (seq ()) => nil.

* Using destructuring instead of (first coll) (next coll). Makes it a
bit shorter and is quite useful if you're going to use the result of
(first coll) or (next coll) multiple times, but in this case it doesn't
really matter.

Christophe Grand

unread,
Nov 7, 2009, 9:17:52 AM11/7/09
to clo...@googlegroups.com
Can I play too?

Non-lazy version (basically the same as Chouser's with reduce instead of loop):
(defn partition-when [pred coll]
(reduce #(if (pred %2)
(conj %1 [%2])
(conj (pop %1) (conj (peek %1) %2)))
[[]] coll))

user=> (partition-when odd? (range 1 15))
[[] [1 2] [3 4] [5 6] [7 8] [9 10] [11 12] [13 14]]
user=> (partition-when even? (range 1 15))
[[1] [2 3] [4 5] [6 7] [8 9] [10 11] [12 13] [14]]


And lazy version:
(defn partition-when [pred coll]
(let [heads (partial take-while (complement pred))]
(cons (heads coll)
(mapcat #(when (pred %1) [(cons %1 (heads %2))])
coll (iterate rest (rest coll))))))

user=> (partition-when odd? (range 1 15))
(() (1 2) (3 4) (5 6) (7 8) (9 10) (11 12) (13 14))
user=> (partition-when even? (range 1 15))
((1) (2 3) (4 5) (6 7) (8 9) (10 11) (12 13) (14))

Christophe
--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.cgrand.net/ (en)

Warren Wood

unread,
Nov 7, 2009, 10:39:41 PM11/7/09
to Clojure


On Nov 6, 12:10 pm, John Harrop <jharrop...@gmail.com> wrote:
> On Fri, Nov 6, 2009 at 1:07 PM, John Harrop <jharrop...@gmail.com> wrote:
> > On Fri, Nov 6, 2009 at 1:01 PM, Warren Wood <warrenthomasw...@yahoo.com>wrote:
>
> >> In the meantime, I came up with the following, which seems to work.
> >> I'm sure it can be improved.
>
> >> (defn NOT [pred] (fn [x] (not (pred x))))
>
> >> ...
>
> > Which leads me to another question, are there standard functions
> >> sitting around somewhere already to do boolean combinations of
> >> predicates (conjoin, disjoin, negate perhaps?)?  Like my NOT above.
>
> > How about complement?
>
> > user=> (def x (complement even?))
> > #'user/x
> > user=> (x 4)
> > false
> > user=> (x 3)
> > true
>
> And WHAT the devil is with complement's messy implementation? (defn
> complement [f] (comp not f)) seems much cleaner. :)

Ok, I'm embarrassed that I didn't find complement. I know I had known
about it at one time. I think I had been wondering about versions of
union and intersection that would apply to predicates as well as sets,
since mathematically a predicate can be deemed as defining the set of
all things for which it is true. I was thinking of a procedure like
AND (again probably not a great name... maybe conjunction would be
better). I don't think anything like comp can be used in this case
since and is a macro.

(defn AND [f g] (fn [x] (and (f x) (g x))))

Is there any standard function like THAT?

Warren Wood

unread,
Nov 7, 2009, 11:12:31 PM11/7/09
to Clojure
Thanks, that was indeed helpful!

Warren Wood

unread,
Nov 8, 2009, 12:02:16 AM11/8/09
to Clojure
Thought of this, which I like better. Again, I'm surprised if
conjunction is not already a standard function, but I can't find it.
I'm still a bit tempted to call it AND for readabilty of code. (I
spent some time studying combinatory logic back in the day. (I even
had a "Curry Fellowship" at Penn State where Haskell Curry used to
work.) Can't remember what combinator letter my "dual" function is.)

(defn dual [x] (fn [f] (f x)))

(defn conjunction [& preds]
(fn [x] (every? (dual x) preds)))

(filter
(conjunction even? (partial >= 16) (partial <= 9))
(range 1 20))

evaluates to

(10 12 14 16)

Edmund Jackson

unread,
Nov 9, 2009, 6:23:22 AM11/9/09
to clo...@googlegroups.com
Here's something based on a similar question I asked in #clojure the
other day, based on the code Chousuke answered with (all ugliness is
my fault).

(defn cond [f pred]
(fn [coll acc outp]
(if (empty? coll)
(conj outp acc)
(if (pred (first coll))
(recur (rest coll) (vector (first coll)) (conj outp acc))
(recur (rest coll) (f acc (first coll)) outp)))))

((cond conj (partial < 5)) [1 2 3 4 5 6 7 9 2 1 2 3] '[] '[])
user=> [[1 2 3 4 5] [6] [7] [9 2 1 2 3]]

Edmund
Edmund

"The future is here. It's just not widely distributed yet"
-- Gibson




jan

unread,
Nov 11, 2009, 7:10:35 PM11/11/09
to clo...@googlegroups.com
Warren Wood <warrenth...@yahoo.com> writes:
> Thought of this, which I like better. Again, I'm surprised if
> conjunction is not already a standard function, but I can't find it.
> I'm still a bit tempted to call it AND for readabilty of code. (I
> spent some time studying combinatory logic back in the day. (I even
> had a "Curry Fellowship" at Penn State where Haskell Curry used to
> work.) Can't remember what combinator letter my "dual" function is.)
>
> (defn dual [x] (fn [f] (f x)))
>
> (defn conjunction [& preds]
> (fn [x] (every? (dual x) preds)))
>
> (filter
> (conjunction even? (partial >= 16) (partial <= 9))
> (range 1 20))

for cases like this you can also leverage reader macros

(filter
#(and (even? %) (>= 16 %) (<= 9 %))
(range 1 20))

--
jan
Reply all
Reply to author
Forward
0 new messages