在Clojure中保留带有索引的有状态查找表的习惯性方法

我对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)))

但是,这似乎并不是很具代表性,因为它基本上是副作用,甚至没有隐藏它。

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


Ankur给出的答案不是线程安全的,尽管我不认为他的描述为什么是非常有用的,而且他的选择更糟糕。 说“我现在不担心多线程”是合理的,在这种情况下,答案是好的。 但是,即使您在任何特定情况下都不需要保证,也能够安全地编写这些内容,这是非常有价值的,唯一安全的方法是在swap!内部完成所有逻辑swap! ,如下所示:

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

你可以通过避免swap!来加速这一点swap! 如果函数被调用时已经有一个条目,并且如果在进入swap!已经有一个条目,则避免使用assoc swap! ,但是你必须“仔细检查”地图在赋值之前没有当前令牌的条目(count %) ,因为在开始swap!之前,其他线程可能会隐藏起来swap! (但在决定swap! ),并为当前令牌分配一个值,在这种情况下,您必须遵守该分配,而不是创建一个新的。

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

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


原子中的单个地图就足够了:

(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,这就是为什么你在你的问题中得到了这个解决方案。 试着从构成表情的角度思考,而不是思考步骤式的强制性风格。

链接地址: http://www.djcxy.com/p/11469.html

上一篇: Idiomatic way of keeping a stateful lookup table with indexes in Clojure

下一篇: Sectioning Fetched Results Controller breaks Search Display