Reputation: 836
I need to provide a validation rule for new element to be added/conj-ed to a collection. This rule will be a specific comparison to elements already existed in collection.
E.g. if my collection is a sorted-set, I need not only unique elements to be accepted by conj but also they should be unique in some other specific case:)
My specific case is as follows (don't put on critique my example:) it's far from reality):
I have a set of ridges ("chains of peeks in mountains") as vectors of their "peeks" at different time:)
E.g.
many many years ago there were one ridge in mountains: #{[1 0 2 0]}
many years ago one more ridge grow: #{[1 0 2 0] [2 0 1 0]}
And now... I want to allow to be added only ridges acceptable by the following rule:
For example in our last case #{[1 0 2 0][2 0 1 0]}
,
Valid ridges are: [0 0 0 1][0 1 0 0][0 1 0 1][3 0 1 0] [1 0 3 0] [1 1 2 0] [2 0 1 1] [2 1 1 0]
, etc...
Invalid ridges are: [1 0 1 0] [0 0 1 0]
, etc...
In short, we accept only ridges with "at least at some place" higher peeks :)
The question is: What's the better way to implement such validation in clojure?
if
with validation function?Upvotes: 1
Views: 99
Reputation: 3010
You could use a pre-condition:
(defn higher-peak? [old-ridge new-ridge]
(some #(> (new-ridge %) (old-ridge %))
(range (count old-ridge))))
(defn conj-ridge [ridges ridge]
{:pre [(some #(higher-peak? % ridge) ridges)]}
(conj ridges ridge))
This will throw an AssertionError whenever you try to use conj-ridge
to add a ridge that does not have a higher peak. If you don't want an error, you could write a higher order function that wraps the call to conj-ridge
in a try
/catch
block:
(defn conj-ridge-if-valid [ridges ridge]
(try
(conj-ridge ridges ridge)
(catch AssertionError e ridges)))
Upvotes: 0
Reputation: 7949
Creating a new collection type is a bit tedious but is a (the?) correct solution (don't forget to implement read/print, tag literals may help).
Performing an if
before each conj
is suboptimal because, for efficiency, you may need to maintain some property on the collection: in your example you may want to keep the min height for each position in the vector (thus allowing to decide whether to add an item in O(1) instead of O(n)).
The least you can do is a conj-ridge
function:
(defn conj-ridge [[ridges min-heights :as ridges-set] ridge]
(if (= (map max min-heights ridge) min-heights)
ridges-set
[(conj ridges ridges) (map min min-heights ridge)]))
Upvotes: 0