I'm having a conceptual problem, thinking functionally, and I was
hoping the fine Clojurians could take a peek.

Boiled down, I need to do a combination of map and reduce on a
collection.  My "walker" needs to look at pairs along the collection,
but then sort of "inject" new values and restart when necessary.
Since I don't know of a "more general" über-HOF that will do what I
want, I tried to write my own function.  It's not tail recursive,
which is a big problem.  So I need help.  But let me back up, and
start where it will make sense.

I need to represent, in their most-reduced form, collections of half-
closed intervals on Z (integers).  So an interval might be [2, 4)
which means 2 and 3 are elements, but 1 and 4 are not.  Since the
collections of intervals must be "reduced", I can't have (using
chicken lips as vector for clarity):

< [2, 4) [4, 8) >

Note that all elements between 2 (inclusive) and 8 (exclusive) satisfy
this collection, so the two intervals must be merged. This collection
should be:

< [2, 8) >

Similarly, all partially or completely overlapping intervals must be
merged.  See how this could be like a map/reduce function?  Sort the
collection.  Look at the first two elements; if they can merge, merge
them and then restart with the newly merged interval at the head.  If
they can't be merged, release the first into the result, and consider
the second and third, etc.

So I'm representing spans (ranges) of integers with a map, like so:

(defn make-span
  "Makes a non-zero-width span of integer value."
  ([start end]
     {:pre [(integer? start) (integer? end) (< start end)]}
     {:start (min start end) :end (max start end)})
  ([singleton]
     (make-span singleton (inc singleton))))

I can tell if these intervals are in some way overlapping or perfectly
adjacent like this:

(defn overlap?
  "Are these two spans partially or totally
   overlapping or perfectly adjacent?"
  [s1 s2]
  (not (or (pos? (- (:start s2) (:end s1)))
           (pos? (- (:start s1) (:end s2))))))

Straightforward so far.  Now, given a *sorted* collection of spans, I
should be able to reduce the collection to its canonical form by
looking at first and second, merging their span if they overlap, and
recurring as necessary:

(defn merge-spans [spans]
  (if (empty? spans) spans
      (let [x (first spans)
            ys (rest spans)]
        (if (empty? ys) spans
            (let [y (first ys)
                  zs (rest ys)]
              (if (overlap? x y)  ; this is the merge
                (let [xy (make-span (min (:start x) (:start y))
                                    (max (:end x) (:end y)))]
                  (recur (into [xy] zs)))
                                        ; this is the recursive
problem
                (into [x] (merge-spans ys))))))))

Let's try some demo code:

(defn safe-merge []
  (let [s1 (make-span 1)
        s2 (make-span 2)
        s3 (make-span 9)
        demo-spans (sort-by :start [s2 s3 s1])]  ; we sort by :start
    (merge-spans demo-spans)))

(defn unsafe-merge []
  (let [bad-spans (sort-by :start (map make-span (range 0 100000 2)))]
    (count (merge-spans bad-spans))))

user=> (safe-merge)
[{:start 1, :end 3} {:start 9, :end 10}]   ; perfect!

user=> (unsafe-merge)
StackOverflowError   clojure.lang.PersistentArrayMap.equalKey
(PersistentArrayMap.java:201)

OK, this is a really long post because of all the code, but thanks for
reading this far.  Can anyone suggest a way for me to either A) get
rid of the ordinary recursion in merge-spans, or B) approach this
problem from a different angle I'm not thinking about?

Thanks in advance everybody…

Mike

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@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+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to