Hey all --
I recently found myself needing to check for pairwise property consistency
across a sequence of elements, similar in spirit to inequality checking (in
this case, date comparisons using clj-time *t/before?*). What I wanted was
to do something like:
(if (t/before? date-a date-b date-c date-d)
; elided
)
...which is symmetric to the idiomatic usage of the inequality functions in
clojure core (*=*, *>* and friends). Unfortunately, *t/before?* does not
support variadic invocation, and after reading the source behind each of
those functions, I discovered that they implement their own variadic
overloads ad hoc, and there doesn't appear to be (at least I haven't yet
discovered) a way to generalize pairwise property assertion over an
arbitrarily large sequence of things.
I would previously have solved this like:
(reduce (fn [_ [x y]]
(if (t/before? x y)
true
(reduced false))) true
(partition 2 1 [(t/local-date 2015 1 1) (t/local-date 2015 2 1)
(t/local-date 2015 3 1)]))
;-> true
...or even...
(every? (fn [[x y]] (t/before? x y)) (partition 2 1 [(t/local-date 2015 1 1)
(t/local-date 2015 2 1) (t/local-date 2015 3 1)]))
...which is sort of cumbersome and occludes my intent. I ended up writing a
function to address this problem generally, and thought it might be useful
to others (and possibly worth including in clojure). Here it is:
(defn pairwise?
"Returns true if every sequential pair satisfies pred; false otherwise."
{:inline (fn [pred x y] `(~pred ~x ~y))
:inline-arities #{3}}
([_ _] true)
([pred x y] (pred x y))
([pred x y & more]
(if (pairwise? pred x y)
(if (next more)
(recur pred y (first more) (next more))
(pairwise? pred y (first more)))
false)))
...and the test cases:
(deftest pairwise?
(testing "Idiomatic cases"
(is (pairwise? > 3 2 1 0))
(is (pairwise? t/after? (t/local-date 2015 3 1) (t/local-date 2015 2 1)
(t/local-date 2015 1 1)))
(is (pairwise? #(= %1 (* -1 %2)) -1 1 -1 1))
(is (not (pairwise? = 1 1 1 2 1))))
(testing "Short circuits on false"
(is (not (apply pairwise? = (concat [2 2] (repeat 1)))))))
It accepts as its first argument any binary predicate, and applies it to a
splat until the predicate returns false. The keen eye will notice it looks
very similar to the variadic overload of *>*, which is what I used for
reference. This means you can now do things like:
(pairwise? t/before? (t/local-date 2015 1 1) (t/local-date 2015 2 1)
(t/local-date 2015 3 1))
;-> true
...even though *t/after?* only supports a dyadic form. This also works
nicely with apply to run against seqs:
(apply pairwise? t/before? [(t/local-date 2015 1 1) (t/local-date 2015 2 1)
(t/local-date 2015 3 1)])
;-> true
Or, for properties that aren't necessarily correlated to ordering:
(pairwise? #(= %1 (* -1 %2)) -1 1 -1 1)
;-> true
Which of course works for the inequality operators (although they obviously
support this natively):
(pairwise? > 3 2 1 0)
;-> true
As i said, I was unable to find anything in clojure core similar to
*pairwise?*, although admittedly I've only been using clojure in anger now
for around 4 months, so if I'm overlooking an in-built capability, please
set me straight.
Thoughts/suggestions/amendments/corrections welcome and appreciated!
Cheers,
-J
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.