Idiomatic Clojure code?

69 views
Skip to first unread message

bc

unread,
Oct 27, 2008, 10:47:37 AM10/27/08
to Clojure
Hi all,

I've put on my blog (http://bc.tech.coop/blog/081027.html) a post
about some Clojure language features. In the post, I point out the
features in a couple of functions that I wrote (which do an "exclusive
or (XOR)" on sequences). Since I'm new to Clojure and still learning
how to do things, I would appreciate any comments on whether the code
could be re-written in a more "idiomatic" manner in Clojure.

Thanks,
Bill

Stuart Halloway

unread,
Oct 27, 2008, 11:12:50 AM10/27/08
to clo...@googlegroups.com
Hi Bill,

You could do something like this:

(defn seq-xor-2-seqs
"Returns the unique values that are in one sequence but not the
other."
[x y]
(let [x (into #{} x)
y (into #{} y)]
(lazy-cat (clojure.set/difference x y)
(clojure.set/difference y x))))

Not sure this is more idiomatic, though. And I guess it would perform
worse with huge collections...

Stuart

Bill Clementson

unread,
Oct 27, 2008, 11:30:31 AM10/27/08
to clo...@googlegroups.com
Hi Stuart,

Thanks, that's a nice alternative approach!

Bill

Chouser

unread,
Oct 27, 2008, 11:46:11 AM10/27/08
to clo...@googlegroups.com
I think it's generally better to use = instead of .equals for
equality, unless you have a specific reason to not use = (which I
don't think is the case here).

On Mon, Oct 27, 2008 at 11:12 AM, Stuart Halloway
<stuart....@gmail.com> wrote:
>
> You could do something like this:
>
> (defn seq-xor-2-seqs
> "Returns the unique values that are in one sequence but not the
> other."
> [x y]
> (let [x (into #{} x)
> y (into #{} y)]
> (lazy-cat (clojure.set/difference x y)
> (clojure.set/difference y x))))
>
> Not sure this is more idiomatic, though. And I guess it would perform
> worse with huge collections...

If I'm reading thing's correctly, Bill's solution is O(n^2), while
Stuart's is O(n*log(n)) or better. It looks like difference iterates
over one seq and for each item does a lookup (nearly constant time) on
the the other, although copying into sets may use more memory.

By the way, difference is eager, so I'm not sure there's much point in
using lazy-cat. :-)

Here's another approach to do all the seqs at once:

(defn seq-xor
"Returns unique values that are in one sequence but not the others."
[& seqs]
(let [obj-cnt (reduce (fn [acc s]
(merge-with + acc (into {} (for [i s] {i 1}))))
{} seqs)]
(for [[obj cnt] obj-cnt :when (== cnt 1)]
obj)))

--Chouser

Bill Clementson

unread,
Oct 27, 2008, 12:04:09 PM10/27/08
to clo...@googlegroups.com
Hi Chouser

On Mon, Oct 27, 2008 at 8:46 AM, Chouser <cho...@gmail.com> wrote:
>
> I think it's generally better to use = instead of .equals for
> equality, unless you have a specific reason to not use = (which I
> don't think is the case here).

Only that it allowed me to talk about Java interop in my blog post. ;-)

Oooh, very nice!

Thanks,
Bill

Stuart Halloway

unread,
Oct 27, 2008, 12:25:28 PM10/27/08
to clo...@googlegroups.com
Chouser,

I think I am missing something here, can you elaborate?

> By the way, difference is eager, so I'm not sure there's much point in
> using lazy-cat. :-)

I am using lazy-cat *because* difference is eager. Is that mistaken?
For example, the first expression below returns immediately, and the
second does not.

(take 1 (lazy-cat [1 2] [(Thread/sleep 10000)]))
(1)
user=> (lazy-cat [1 2] [(Thread/sleep 10000)])
(1 2 nil)

Stuart

Chouser

unread,
Oct 27, 2008, 12:52:15 PM10/27/08
to clo...@googlegroups.com

Hm, no, you've got a good point. I guess I didn't put much thought
into that statement.

I guess I was thinking that in order to return anything at all, your
function already copied both seqs into sets and ran difference once,
and so holding off on the second run of difference wasn't worth much.
But I guess that may be an inaccurate assumption. Also, I try to be
careful to use lazy outer functions when using lazy inner functions,
in order to prevent an outer eager function from destroying the
laziness of the inner -- but I was wrong in jumping to the conclusion
eager inner funcs suggest the use of eager outer funcs.

On the other hand by using lazy-cat, the returned object is going to
keep references to both full set objects, so that the difference can
be computed later. Using an eager concat would allow the larger sets
to be gc'ed.

Though upon examination of boot.clj, it looks like "concat" is lazy
also. And now I'm wondering what the difference is between concat and
lazy-cat.

So anyway, you're right -- lazy-cat may make sense with eager inner
funcs in general and in this case in particular.

--Chouser

Rich Hickey

unread,
Oct 27, 2008, 2:31:21 PM10/27/08
to Clojure


On Oct 27, 12:04 pm, "Bill Clementson" <billc...@gmail.com> wrote:
> Hi Chouser
>
Here's a simple one with set ops:

(alias 'set 'clojure.set)

(defn set-xor [& sets]
(second (reduce
(fn [[all ret] s]
[(set/union all s) (set/union (set/difference ret s)
(set/difference s all))])
[#{} #{}] sets)))

(defn seq-xor [& seqs] (seq (apply set-xor (map set seqs))))

Rich

Bill Clementson

unread,
Oct 27, 2008, 3:15:26 PM10/27/08
to clo...@googlegroups.com

Yet another clever alternative!

Thanks guys!

- Bill

Rich Hickey

unread,
Oct 27, 2008, 7:06:49 PM10/27/08
to Clojure


On Oct 27, 3:15 pm, "Bill Clementson" <billc...@gmail.com> wrote:
One more, inspired by Chouser's, one pass, no counting:

(defn seq-xor [& seqs]
(seq (second
(reduce (fn [[all ret] x]
(if (contains? all x)
[all (disj ret x)]
[(conj all x) (conj ret x)]))
[#{} #{}] (mapcat distinct seqs)))))

One take away from all of these is that use of sets, maps and vectors
is very idiomatic Clojure, and the sooner you can add them to the
lists in your toolbox the more productive you'll be with Clojure.

Thanks so much for the blog series!

Rich

Bill Clementson

unread,
Oct 27, 2008, 7:36:18 PM10/27/08
to clo...@googlegroups.com

Excellent summarization! I've updated my blog post with the examples
and used your comment as the 'closer' for the blog post.

> Thanks so much for the blog series!

Thank you. I'm having lots of fun with Clojure!

- Bill

Reply all
Reply to author
Forward
0 new messages