构建一个懒惰的,不纯的id生成器

时间:2013-01-04 01:01:28

标签: clojure

我想知道如何在Clojure中创建一个无限的,不纯的唯一值序列。

(def generator ...) ; def, not defn
(take 4 generator) ; => (1 2 3 4)
(take 4 generator) ; => (5 6 7 8). note the generator's impurity.

我认为这样的设计可能比例如将单个整数值包装到引用类型中并将其从其使用者中递增,如下所示:

  • 建议的方法将实现细节简化为单一变化点:生成器。否则所有消费者都必须关心引用类型(atom)和提供下一个值的具体函数(inc
  • 序列可以利用许多clojure.core函数。 “手动”从原子构建一个id列表会有点笨重:(take 4 (repeatedly #(swap! _ inc)))

我无法想出一个有效的实施方案。 有可能吗?

7 个答案:

答案 0 :(得分:6)

你可以在一个不纯的类(如java.util.concurrent.atomic.AtomicLong)周围包装一个惰性序列来创建一个id序列:

(def id-counter (java.util.concurrent.atomic.AtomicLong.))

(defn id-gen []
  (cons
   (.getAndIncrement id-counter)
   (lazy-seq
     (id-gen))))

这样可行,但前提是您不保存序列的头部。如果你创建一个捕获头部的var:

(def id-seq (id-gen))

然后反复调用它,它将从序列的开头返回id,因为你已经抓住了序列的头部:

(take 3 id-seq)
;; => (0 1 2)
(take 3 id-seq)
;; => (0 1 2)
(take 3 id-seq)
;; => (0 1 2)

如果重新创建序列,由于杂质,您将获得新值:

(take 3 (id-gen))
;; (3 4 5)
(take 3 (id-gen))
;; (6 7 8)
(take 3 (id-gen))
;; (9 10 11)

我建议您仅出于教育目的(而非生产代码)执行以下操作,但您可以创建自己的ISeq实例,以更直接地实现杂质:

(def custom-seq
     (reify clojure.lang.ISeq
            (first [this] (.getAndIncrement id-counter))
            (next  [this] (.getAndIncrement id-counter))
            (cons  [this thing]
                   (cons thing this))
            (more [this] (cons
                          (.getAndIncrement id-counter)
                          this))
            (count [this] (throw (RuntimeException. "count: not supported")))
            (empty [this] (throw (RuntimeException. "empty: not supported")))
            (equiv [this obj] (throw (RuntimeException. "equiv: not supported")))
            (seq   [this] this)))

(take 3 custom-seq)
;; (12 13 14)
(take 3 custom-seq)
;; (15 16 17)

答案 1 :(得分:2)

在回答你的问题时我发现了一些有趣的事情。发生在我身上的第一件事就是,对于你需要这些ID的最终目标,gensym函数可能会有所帮助。

然后,我想"嘿嘿,这似乎增加了一些不纯的计数器来生成新的ID"和#34;嘿,源代码中的内容是什么?"这让我想到了这个:

(. clojure.lang.RT (nextID))

这似乎做你需要的。凉!如果你想按照你的建议使用它,那么我可能会把它变成一个函数:

(defn generate-id []
  (. clojure.lang.RT (nextID)))

然后你可以这样做:

user> (repeatedly 5 generate-id)
=> (372 373 374 375 376)

我还没有测试过这是否会产生永远独特的价值观"全球" - 我对术语不确定,但我在谈论你什么时候可能在不同的线程中使用此generate-id函数,但仍希望确保它生成唯一值。

答案 2 :(得分:1)

这是另一种解决方案,可能:

user=> (defn positive-numbers
          ([] (positive-numbers 1))
          ([n] (cons n (lazy-seq (positive-numbers (inc n))))))
#'user/positive-numbers
user=> (take 4 (positive-numbers))
(1 2 3 4)
user=> (take 4 (positive-numbers 5))
(5 6 7 8)

答案 3 :(得分:0)

一种更惯用,线程安全,并且不会引起头部引用的怪异的方法是在可变引用中构建的一个clojures上使用闭包。以下是我遇到同样问题后的一个快速示例。它只是关闭参考。

(def id-generator (let [counter (ref 0)]
                (fn [] (dosync (let [cur-val @counter] 
                         (do (alter counter + 1)
                           cur-val))))))

每次拨打(id-generator),您都会获得序列中的下一个号码。

答案 4 :(得分:0)

这是另一种快捷方式:

user> (defn make-generator [& [ii  init]]
  (let [a (atom (or ii 0 ))
        f #(swap! a inc)]
    #(repeatedly f)))
#'user/make-generator
user> (def g (make-generator))
#'user/g
user> (take 3 (g))
(1 2 3)
user> (take 3 (g))
(4 5 6)
user> (take 3 (g))
(7 8 9)

答案 5 :(得分:0)

这是黑客攻击,但它很有效,而且非常简单

; there be dragons !
(defn id-gen [n] (repeatedly n (fn [] (hash #()))))
(id-gen 3) ; (2133991908 877609209 1060288067 442239263 274390974)

基本上,clojure创造了一个匿名的“匿名”。函数,但由于clojure本身需要一个名称,它使用唯一不纯的id来避免碰撞。如果您散列一个唯一的名称,那么您应该得到一个唯一的数字。

希望有所帮助

答案 6 :(得分:0)

从任意种子标识符集合创建标识符:

(defonce ^:private counter (volatile! 0))

(defn- next-int []
  (vswap! counter inc))

(defn- char-range
  [a b]
  (mapv char
        (range (int a) (int b))))

(defn- unique-id-gen
  "Generates a sequence of unique identifiers seeded with ids sequence"
  [ids]
  ;; Laziness ftw:
  (apply concat
         (iterate (fn [xs]
                    (for [x xs
                          y ids]
                      (str x y)))
                  (map str ids))))

(def inf-ids-seq (unique-id-gen (concat (char-range \a \z)
                                        (char-range \A \Z)
                                        (char-range \0 \9)
                                        [\_ \-])))

(defn- new-class
  "Returns an unused new classname"
  []
  (nth inf-ids-seq (next-int)))

(repeatedly 10 new-class)

演示:

(take 16 (unique-id-gen [\a 8 \c]))
;; => ("a" "8" "c" "aa" "a8" "ac" "8a" "88" "8c" "ca" "c8" "cc" "aaa" "aa8" "aac" "a8a")