10

我想知道如何在 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)))

我想不出一个可行的实现。有可能吗?

4

7 回答 7

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)
于 2013-01-04T03:04:00.213 回答
2

在回答您的问题时,我发现了一些有趣的东西。我首先想到的是,对于您需要这些 ID 的任何最终目标,该gensym功能可能会有所帮助。

然后,我想“嗯,嘿,这似乎增加了一些不纯的计数器来生成新的 ID”和“嗯,嘿,源代码中有什么?” 这导致我这样做:

(. clojure.lang.RT (nextID))

这似乎可以满足您的需求。凉爽的!如果您想按照建议的方式使用它,那么我可能会将其设为函数:

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

然后你可以这样做:

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

我还没有测试这是否会“全局”产生始终唯一的值——我不确定术语,但我说的是你什么时候可能generate-id在不同的线程中使用这个函数,但仍然想确定它正在产生独特的价值。

于 2013-01-04T02:51:49.030 回答
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)
于 2013-01-04T02:35:28.277 回答
0

一种更惯用、线程安全且不会对头部引用产生奇怪影响的方法是对内置于可变引用中的 clojures 之一使用闭包。这是我遇到相同问题后处理的一个快速示例。它只是关闭了一个参考。

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

每次调用 (id-generator) 时,您都会得到序列中的下一个数字。

于 2013-12-21T04:03:42.587 回答
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)
于 2014-07-12T04:40:37.877 回答
0

这是 hack,但它有效,而且非常简单

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

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

希望能帮助到你

于 2016-02-09T10:56:12.410 回答
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")
于 2016-12-13T16:41:34.373 回答