【问题标题】:Building a lazy, impure id generator构建一个惰性的、不纯的 id 生成器
【发布时间】:2013-01-04 01:01:27
【问题描述】:

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

我认为这样的设计可能比 e.g. 更方便。将单个整数值包装到引用类型中并从其消费者处递增,如下所示:

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

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

【问题讨论】:

  • (repeatedly gensym)。不是你要找的,我知道。或者:(repeatedly #(gensym "")),生成看起来像数字的符号。
  • 或实际数字:(repeatedly #(Long. (str (gensym ""))))))

标签: clojure


【解决方案1】:

您可以在不纯类(如 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)

【讨论】:

  • 太棒了 :) 虽然我通常看重不变性,但我想知道如果我们选择具体化 ISeq,应用程序的正确性会受到哪些方面的影响。
  • 你能详细说明一下吗?需要改变什么? (感谢您指出这一点)
【解决方案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 函数,但仍想确保它产生独特的价值。

【讨论】:

  • +1 用于深入了解 Clojure impl,我只是喜欢学习它的工作原理。但是在(def a (repeatedly generate-id)) 之后,(take 5 a) 将始终返回相同的...
  • 再看一遍,我发现我的答案只是做你已经说过你不想要的事情的另一种方式。
【解决方案3】:

这是另一种解决方案,也许:

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)

【讨论】:

  • 我不认为你的解决方案(无论如何这很有趣)符合我的要求 - 它是纯粹的!
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

    这是另一种快捷方式:

    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)
    

    【讨论】:

      【解决方案6】:

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

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

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

      希望对你有帮助

      【讨论】:

        【解决方案7】:

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

        (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")
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-09-13
          • 1970-01-01
          • 1970-01-01
          • 2011-03-29
          • 2013-11-28
          • 1970-01-01
          • 1970-01-01
          • 2020-12-07
          相关资源
          最近更新 更多