Here is a bag implementation:

(defprotocol SetOps
  (disjoin* [this obj])
  (has* [this obj])
  (total [this obj])
  (counts [this]))

(defn disjoin
  ([s] s)
  ([s obj]
    (disjoin* s obj))
  ([s obj & more]
    (apply disjoin (disjoin s obj) more)))

(defn difference
  ([s] s)
  ([s other]
    (apply disjoin s other))
  ([s other & more]
    (apply difference (difference s other) more)))

(defn intersection
  ([s] s)
  ([s other]
    (difference s (difference s other)))
  ([s other & more]
    (apply intersection (intersection s other) more)))

(defn union
  ([s] s)
  ([s other]
    (if (empty? other) s (apply conj s other)))
  ([s other & more]
    (apply union (union s other) more)))

(defn has?
  ([s] true)
  ([s obj]
    (has* s obj))
  ([s obj & more]
    (and (has? s obj) (apply has? s more))))

(extend-type clojure.lang.IPersistentSet
  SetOps
    (disjoin* [this obj] (disj this obj))
    (has* [this obj] (contains? this obj))
    (total [this obj] (if (contains? this obj) 1 0))
    (counts [this] (zipmap this (repeat 1))))

(deftype Bag [m c]
  clojure.lang.IObj
    (withMeta [this, mtd]
      (Bag. (with-meta m mtd) c))
    (meta [this] (meta m))
  clojure.lang.IPersistentCollection
    (count [this] c)
    (empty [this] (Bag. {} 0))
    (equiv [this other] (= (seq this) other))
    (seq [this]
      (mapcat
        (fn [[k v]]
          (repeat v k))
        m))
    (cons [this obj]
      (Bag. (merge-with + m {obj 1}) (inc c)))
  SetOps
    (disjoin* [this obj]
      (if-let [n (m obj)]
        (if (= 1 n)
          (Bag. (dissoc m obj) (dec c))
          (Bag. (assoc m obj (dec n)) (dec c)))
        this))
    (has* [this obj] (contains? m obj))
    (total [this obj] (m obj 0))
    (counts [this] m)
  Object
    (toString [this]
      (apply str
        (interpose " "
          (seq this)))))

(defn bag [& objects]
  (Bag. (frequencies objects) (count objects)))



user=> (difference (bag 1 6 3 6 8 4 5 6 6 9 9) (bag 9 9 6 8 1))
#<Bag 6 6 6 3 4 5>

The difference, union, disjoin, intersection etc. functions are also
made to work on Clojure sets, and via extend-type or extend-protocol
can be extended to other types. (I didn't make Bag implement
IPersistentSet because I considered nonduplication to be part of the
latter's contract. So SetOps is a broader umbrella: unordered
collection with efficient membership test.)

The has? function is like contains?, but works on bags and sets rather
than on sets and maps and may take more arguments, in which case it
checks for all being contained. Note that (has? (bag 1) 1 1 1) will
return true; to check for containing at least a certain number of
occurrences use (not (empty? (difference bag-1 bag-2))) where bag-2
has the items we want to check for in the appropriate quantities.

Well, except that count and empty are broken for some reason:

user=> (.count (Bag. {} 0))
0
user=> (count (Bag. {} 0))
1

I don't understand what's causing this, but empty bags are always
returning a count of 1 (and false from empty?) although the .count
method correctly returns zero. I've traced the problem as far as the
Java method clojure.lang.RT/count but no farther at this time.

-- 
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