On Thu, Jul 16, 2009 at 1:52 AM, John Harrop<jharrop...@gmail.com> wrote: > On Wed, Jul 15, 2009 at 12:58 PM, Mark Engelberg <mark.engelb...@gmail.com> > wrote: >> >> It looks like stack-rot is going to be the bottleneck in your app >> since it requires traversing the whole vector to build the new one, >> but I think the list-based implementation would be a bit worse, so I >> think your choice to use vectors here is sound. >> >> For stack-dropn and stack-rot to both be fast, I think that you really >> want to use a deque. Unfortunately, Clojure doesn't have a built-in >> deque, and I don't see one in contrib, but you could look at >> clojure.lang.PersistentQueue and read Okasaki's Functional Data >> Structures to draw some inspiration. > > What's needed here is a ring buffer. It shouldn't be hard to implement one > atop a pair of a clojure vec, a true-count, and a start-offset. Rotation is > then very cheap: replace v with (assoc v (- (+ start-offset true-count) > (count v)) (get v start-offset)) and start-offset with (let [s-o (inc > start-offset)] (if (= (count v) s-o) 0 s-o)). Dropn is similarly: true-count > becomes (- n true-count) and start-offset (mod (+ start-offset n) (count > v)). Indexing is cheap: (get v (mod (+ start-offset (mod index true-count)) > (count v))) and peek is even cheaper: (get v start-offset). (Maybe add code > to handle empty, that is, (= 0 true-count)). The tricky thing is push. If (= > true-count (count v)) the vector needs to be copied to a larger vector. You > might want to double the size, leaving the unused elements nil: v becomes > (vec (concat (drop start-offset v) (take start-offset > v) [new-element] (repeat true-count nil))), true-count is inc'd as normal > for a push, and start-offset reset to zero. Doubling the size whenever the > vector must grow causes the cost of copying the vector to be asymptotically > constant per push, instead of linear in the average size of the vector at > the time of a push. > (Ironically, the vector probably has this behavior under the hood, like > java.util.ArrayList, but we need it again because (vec (concat (drop > start-offset v) (take start-offset v) [new-element] (repeat true-count > nil))) has linear time-complexity in vector length.) > You'd actually want to use a structmap for the above if you want to have > multiple ring buffers, with struct keys :count, :offset, and :vector or > similarly. Depending on the application, you might want to make the values > for these keys be refs and have the ring buffer operations use dosync, > though you could instead use a single ref per instance of the structmap. >
FWIW since Java 1.6 there's a java.util.Deque (implemented as RingBuffer) interface with java.util.ArrayDeque implementation, and a java.util.concurrent.BlockingDeque interface with java.util.concurrent.LinkedBlockingDeque implementation. With the Deque interface, you can rotate both directions pretty easily and the rest of the stack like methods are pretty straightforward: (import '(java.util ArrayDeque)) (let [deque (ArrayDeque. '(1 2 3 4))] (println deque) ; rot forward (.addLast deque (.removeFirst deque)) (println deque) ; rot backward (.addFirst deque (.removeLast deque)) (println deque)) But then again, it's a Java collection and not a Clojure one. Cheers, Daniel --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---