Skip to content

Instantly share code, notes, and snippets.

@joinr
Created May 28, 2019 20:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joinr/98093bf0aaea567f5ae7cd80be832a95 to your computer and use it in GitHub Desktop.
Save joinr/98093bf0aaea567f5ae7cd80be832a95 to your computer and use it in GitHub Desktop.
examples of composeable ordering functions
(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)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment