Skip to content

Instantly share code, notes, and snippets.

@dustingetz
Forked from joinr/ordering.clj
Created May 30, 2019 12:46
Show Gist options
  • Select an option

  • Save dustingetz/0f36073e7a7a8026844be004d26546b3 to your computer and use it in GitHub Desktop.

Select an option

Save dustingetz/0f36073e7a7a8026844be004d26546b3 to your computer and use it in GitHub Desktop.

Revisions

  1. @joinr joinr created this gist May 28, 2019.
    123 changes: 123 additions & 0 deletions ordering.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,123 @@
    (ns ordering)
    ;;We want to pack some information along with
    ;;our functions so that when our interpreter picks them
    ;;up, we can determine if the function should be applied
    ;;directly as a comparator, or if we need to "lift"
    ;;it into the comparator domain.

    (defn ordering? [x] (get (meta x) :ordering))
    ;;convenience macro to help us create functions with
    ;;ordering specified in meta
    (defmacro ord-fn [[l r] & body]
    `(vary-meta (fn [~l ~r] ~@body) assoc :ordering true))


    ;;Comparison combinator.

    ;;defines an ordering function from one or more functions. If more
    ;;than one ordering criteria is supplied, the resulting comparison
    ;;will occur from "left to right" in the order of inputs.

    ;;If a function satisfies ordering?, then we leave it as-is. If it's
    ;;otherwise a clojure IFn, then we lift it into the comparator space
    ;;by defining an ordering function, which uses the function as its
    ;;comparison key (similar to sort-by, but composable).


    ;; (ordering o1 o2 f3) ;;more or less imples...

    ;; ^{:ordering true} (fn [l r]
    ;; (let [res1 (o1 l r)]
    ;; (if-not (zero? res)
    ;; res
    ;; (let [res2 (o2 l r)]
    ;; (if-not (zero? res)
    ;; res
    ;; (let [res3 (compare (f3 l) (f3 r))]
    ;; res3))))))

    ;;The result is itself an ordering function, which can again
    ;;be composed via ordering in other sorting criteria.

    (defn ordering
    ([f] (if (ordering? f) f (ord-fn [l r] (compare (f l) (f r)))))
    ([f & fs]
    (let [fs (into [f] fs)]
    (ord-fn [l r]
    (reduce (fn [acc f]
    (let [res (if (ordering? f) (f l r)
    (compare (f l) (f r)))]
    (if (not (zero? res))
    (reduced res)
    acc))) 0 fs)))))

    ;;convenience wrapper to allow us to encode
    ;;orderings as keywords, functions, and vectors.
    (defn eval-order [xs]
    (cond (or (fn? xs) (keyword? xs)) (ordering xs)
    (vector? xs) (apply ordering
    (reduce (fn [acc f]
    (conj acc (eval-order f))) [] xs))
    (nil? xs) nil
    :else (throw (Exception. (str "Unknown ordering expression: " xs)))))


    ;;Convenience function to flip or invert the ordering criteria
    (defn flip [f]
    (if (keyword? f)
    (ord-fn [l r] (compare (f r) (f l)))
    (ord-fn [l r] (f r l))))

    ;;descending order is synonymouse with flipping the inputs
    ;;to an ordering.
    (def descending flip)


    (comment
    ;;testing
    (def xs [{:first "Bilbo", :last "Baggins", :age 900, :looks 45, :index 0 :class :even}
    {:first "James", :last "Kirk", :age 50, :looks 50, :index 1 :class :odd }
    {:first "Benjamin", :last "Button", :age 2, :looks 70, :index 2 :class :even}
    {:first "Benjamin", :last "Franklin", :age 70, :looks 100, :index 3 :class :odd}
    {:first "James" :last "John" :age 50 :looks 50 :index 4 :class :even}
    {:first "James" :last "Jamison" :age 50 :looks 50 :index 4 :class :odd}])

    (sort (eval-order :age) xs)
    (sort (eval-order [:age :looks]) xs)
    (sort (eval-order [:first :last (descending :age)]) xs)

    ;;we can alternately store rules in named functions and compose
    ;;them. This is particularly useful if we define macros to flesh out
    ;;our rules for us, e.g. min-age, max-age, etc.
    (def youngest (eval-order :age))
    (def age-then-looks (eval-order [:age :looks]))
    (def first-last-oldest (eval-order [first last (descending :age)]))
    (sort (eval-order [youngest (descending :looks) :index]) xs)
    (sort (eval-order [:class first-last-oldest]) xs)
    ;;also just inject arbitrary function, use the length of the
    ;;last name
    (def last-count (comp count :last))
    (sort (eval-order [:class :first last-count]) xs)
    (sort (eval-order [:class :first (comp - last-count)]) xs)
    ;;define some string comparing functions
    (def history
    {"Benjamin" "Franklin"})

    (def trek
    {"James" "Kirk"})

    (def fantasy
    {"Bilbo" "Baggins"})

    (defn matches [db m k1 k2]
    (= (some-> k1 m db)
    (some-> k2 m)))

    (def famous (eval-order [#(matches history % :first :last)
    #(matches trek % :first :last)
    #(matches fantasy % :first :last)]))

    (sort (eval-order [(flip famous) first-last-oldest]) xs)


    )