7

我对 Clojure 和一般的函数式编程相当陌生,我一直在努力解决以下问题。我想为一系列标记(字符串)分配一个唯一且稳定的索引。由于查找比插入多得多,因此哈希映射似乎是要走的路。

在Java中,我会写一些类似的东西

int last = 0; 
HashMap<String, Integer> lut = new HashMap<String, Integer>();

function Integer getIndex(String token) {
   Integer index = lut.get(token); 
   if(index == null) 
      last++;
      lut.put(token, last);
      return last;
    else { 
      return index;
    }
}

Clojure 中的音译版本类似于

(def last-index (atom 0))
(def lookup-table (atom {}))

(defn get-index [token]
  (if (nil? (get @lookup-table token))
    (do 
      (swap! last-index inc)
      (swap! lookup-table assoc token @last-index)
      @last-index)
    (get @lookup-table token)))

但这似乎不是很典型,因为它基本上是副作用,甚至没有隐藏它。

那么如果没有两个原子来保持状态,你将如何做到这一点?

4

2 回答 2

3

Ankur 给出的答案不是线程安全的,尽管我不认为 seh 对原因的描述很有帮助,而且他的替代方案更糟。说“好吧,我现在不担心多个线程”是合理的,在这种情况下,这个答案很好。但是,即使您在任何特定情况下都不需要这种保证,也能够安全地编写这些东西是很有价值的,唯一安全的方法是在 内部执行所有逻辑swap!,如下所示:

(let [m (atom {})]
  (defn get-index [token]
    (get (swap! m
                #(assoc % token (or (% token) (count %))))
         token)))

swap!如果在调用函数时已经有一个条目,swap!您可以通过避免 a加快这一速度在分配它之前没有当前令牌的条目(count %),因为其他一些线程可能在您开始之前swap!(但在您决定之后swap!)已经潜入,并为当前令牌分配了一个值,在这种情况下您必须尊重那个任务,而不是做一个新的。

编辑:顺便说一句,Java 版本当然有同样的线程安全问题,因为默认情况下,Java 中的所有内容都是可变的而不是线程安全的。至少在 Clojure 中,你必须!在其中添加一个,说“是的,我知道这很危险,我知道我在做什么。”

所以从某种意义上说,Ankur 的解决方案是对 Java 代码的完美翻译,但更好的是改进它!

于 2012-11-17T04:32:09.823 回答
0

atom 中的单个映射就足够了:

(def m (atom {}))
;adding new string to map
(swap! m #(assoc %1 "Hello" (count %)))
;get an index
(@m "Hello")

(defn get-index [token] 
    (or (@m token) 
        ((swap! m #(assoc %1 token (count %))) token)))

您基本上尝试将 Java 命令式代码映射到 clojure,这就是您在问题中得到该解决方案的原因。尝试从组合表达式的角度进行思考,而不是逐步思考命令式风格。

于 2012-11-16T11:31:34.440 回答