sort-by reverse order?

8,430 views
Skip to first unread message

Alex Baranosky

unread,
Nov 21, 2010, 11:02:51 PM11/21/10
to clo...@googlegroups.com
Hi guys,

I'm trying to figure out how to use sort-by in reverse order.

something like: 

(defn keyfn [p] 
    (:last-name p))

(sort-by keyfn persons)

where persons is a map...

I don't see it in the docs, what's the idiomatic way to do this?

Thanks,
Alex

Ken Wesson

unread,
Nov 21, 2010, 11:27:40 PM11/21/10
to clo...@googlegroups.com

What's wrong with just using (reverse (sort-by foo my-map))?

When you need a seq view, you can wrap it in reverse. When you don't,
the sort order is irrelevant anyway. (For a reverse-sorted seq of just
the keys, (reverse (sort-by foo (keys my-map))) works of course.)

Note that sorting a map sorts by keys; if the map is persons to
something else (e.g. account details or something) then you can sort
it by last name, or if the keys are things like {:first-name "foo"
:last-name "bar"} and the values are whole persons, but if you're
trying to sort on a field of the values that isn't a field of the
keys, you've got bigger problems than reversing the sort order.

Glen Stampoultzis

unread,
Nov 21, 2010, 11:30:45 PM11/21/10
to clo...@googlegroups.com
Not sure if this is the most idiomatic way but there's always:

(reverse (sort-by :last-name persons))

or

(sort-by :last-name #(compare %2 %1) persons)

 

Alex Osborne

unread,
Nov 21, 2010, 11:31:20 PM11/21/10
to clo...@googlegroups.com
Alex Baranosky <alexander...@gmail.com> writes:

> I'm trying to figure out how to use sort-by in reverse order.

I tend to do this:

(sort-by :foo #(compare %2 %1) coll)

Glen Stampoultzis

unread,
Nov 21, 2010, 11:40:11 PM11/21/10
to clo...@googlegroups.com
Actually having put forward that second example there I'm not sure how it actually works.  The docs suggest that the 2nd parameter needs to implement Comparator (peeking at the source confirms this) but compare returns back a number.  It is possible this might be just working by accident.

- Glen

Alex Osborne

unread,
Nov 21, 2010, 11:52:40 PM11/21/10
to clo...@googlegroups.com
Glen Stampoultzis <gst...@gmail.com> writes:

> (sort-by :last-name #(compare %2 %1) persons)
>
>
> Actually having put forward that second example there I'm not sure how
> it actually works. The docs suggest that the 2nd parameter needs to
> implement Comparator (peeking at the source confirms this) but compare
> returns back a number. It is possible this might be just working by
> accident.

For convenience all Clojure fns implement Comparator (as well as
Callable and Runnable). Documented here:

http://clojure.org/special_forms#fn

Alex Baranosky

unread,
Nov 22, 2010, 12:02:13 AM11/22/10
to clo...@googlegroups.com
So I really need to be able to specify the order be either ascending or descending order, and thus to be able to have a mix of orders.

I guess that means I will need to use:

this
(sort-by :last-name #(compare %2 %1) persons)

or
(sort-by :last-name #(compare %1 %2) persons)

depending on the parameters...

Let me get to working on that and get back to you all.

Thanks,
Alex

Alex Baranosky

unread,
Nov 22, 2010, 12:07:54 AM11/22/10
to clo...@googlegroups.com
So for the case I had that method worked.  I wonder though if I had wanted to sort by multiple keys, with some of the keys sorting in reverse order and others in regular order, how I could do that...  Say last name ascending, date of birth descending for example.

David Sletten

unread,
Nov 22, 2010, 12:19:46 AM11/22/10
to clo...@googlegroups.com
Alex,

There might be some useful info here:


Have all good days,
David Sletten


On Nov 22, 2010, at 12:07 AM, Alex Baranosky wrote:

So for the case I had that method worked.  I wonder though if I had wanted to sort by multiple keys, with some of the keys sorting in reverse order and others in regular order, how I could do that...  Say last name ascending, date of birth descending for example.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en





Alex Osborne

unread,
Nov 22, 2010, 12:35:25 AM11/22/10
to clo...@googlegroups.com
Alex Baranosky <alexander...@gmail.com> writes:

Write your own compare function which does that. For example, for the
general case:

(def asc compare)
(def desc #(compare %2 %1))

(defn compare-by [[k comp & more] x y]
(let [result (comp (k x) (k y))]
(if (and (zero? result) (seq more))
(recur more x y)
result)))

(sort #(compare-by [:last-name asc, :date-of-birth desc] %1 %2) coll)

Ken Wesson

unread,
Nov 22, 2010, 12:51:03 AM11/22/10
to clo...@googlegroups.com

+1

Tyler Perkins

unread,
Nov 23, 2010, 4:03:37 PM11/23/10
to Clojure
Nice! And with just a bit more, we have a clean, sorting DSL:

(def asc compare)
(def desc #(compare %2 %1))
;; compare-by generates a Comparator:
(defn compare-by [& key-cmp-pairs]
(fn [x y]
(loop [[k cmp & more] key-cmp-pairs]
(let [result (cmp (k x) (k y))]
(if (and (zero? result) more)
(recur more)
result)))))

(sort (compare-by :last-name asc, :date-of-birth desc) coll)

Alex Baranosky

unread,
Nov 24, 2010, 6:53:58 PM11/24/10
to clo...@googlegroups.com
Very nice

Alex Baranosky

unread,
Nov 29, 2010, 10:35:16 PM11/29/10
to clo...@googlegroups.com
I had some fun with this and added assert-args:

(defmacro assert-args [fnname pred msg & pred-msg-pairs]
  `(when-not ~pred
    (throw (IllegalArgumentException. ~(str fnname " requires " msg))))
  (when (seq pred-msg-pairs)
    (list* `assert-args fnname pred-msg-pairs)))

(def ascending compare)
(def descending #(compare %2 %1))

(defn compare-by [& key-cmp-pairs]
  (assert-args compare-by
    (even? (count key-cmp-pairs)) "even number of args (keyword, comparator)"
    (every? #(or (keyword? %) (fn? %)) key-cmp-pairs) "all args to be keywords or functions")

Sean Devlin

unread,
Nov 30, 2010, 10:07:40 AM11/30/10
to Clojure
If you like assert args, you should check out pre & post conditions
for your functions. They're built in to Clojure since 1.1

The official docs:
http://clojure.org/special_forms#Special%20Forms--%28fn%20name?%20%28[params*%20]%20condition-map?%20exprs*%29+%29

Video Tutorial:
http://vimeo.com/8399758

On Nov 29, 10:35 pm, Alex Baranosky <alexander.barano...@gmail.com>
wrote:

Alex Baranosky

unread,
Nov 30, 2010, 2:24:57 PM11/30/10
to clo...@googlegroups.com
Thanks Sean!

I've come up with this variation:

(def ascending compare)
(def descending #(compare %2 %1))

(defn compare-by [& key-cmp-pairs]
  (fn [x y]
    (loop [[k cmp & more] key-cmp-pairs]
      {:pre [(keyword? k), (fn? cmp), (even? (count more))]}
      (let [result (cmp (k x) (k y))]
        (if (and (zero? result) more)
          (recur more)
          result)))))

I'm not really sure what I think of preconditions yet, but I'm enjoying playing with the idea.

Alex

Julian Jelfs

unread,
Dec 23, 2014, 12:04:35 PM12/23/14
to clo...@googlegroups.com
That's awesome! (though I'm slightly surprised there isn't an easier way). 

Thanks. 

Matthew O. Smith

unread,
Oct 14, 2020, 10:58:30 AM10/14/20
to Clojure
Google brought this up in 2020

Try
(comp - compare)

Andy Fingerhut

unread,
Oct 14, 2020, 1:02:52 PM10/14/20
to clo...@googlegroups.com
This document goes into fairly deep dive on other ways to do it, including a gotcha on edge cases of using something like (comp - compare) that will rarely if ever bite you, but some people may want to know about them to avoid them.


Andy

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/769d3b44-e1dc-43f4-8728-7ecfdce86e79n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages