2012-11-16 3 views
7

私はClojureと関数型プログラミングにはかなり新しく、次の問題に苦しんでいます。一連のトークン(文字列)にユニークで安定したインデックスを割り当てたいと思います。挿入よりも多くのルックアップが存在するため、ハッシュマップが移動する方法のように見えます。Clojureでインデックス付きのステートフルルックアップテーブルを保持する慣習的な方法

私は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))) 

ようなものになるだろう。しかし、これはので、非常にidomaticではないようです

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; 
    } 
} 

の線に沿って何かを書いただろうJavaでは

基本的に副作用があり、隠すことさえありません。

したがって、状態を保持するために2つの原子を持たないとどうしますか?原子内

+1

お互いに依存する2つのアトムを変更するこのようなコードを望むことは決してありません。原子は独立した状態です。お互いに依存する複数のものを変更する必要がある場合は、refsとdosyncを使用する必要があります。 Ankurの答えを考えれば特に重要ではありませんが、心に留めておいてください。 – Rayne

答えて

3

Ankurによって与えられた答えはスレッドセーフではありませんが、私はなぜsehの説明が非常に有用であり、彼の選択肢は悪いとは思わないと思います。 「今は複数のスレッドについて心配していません」と言うのは妥当です。この場合、その答えは問題ありません。しかし、それはあなたが任意の特定のインスタンスであることを保証する必要がない場合でも、安全なものを書くことができるようにするために貴重だし、唯一の安全な方法はそうのように、swap!内のすべてのロジックを実行することです。

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

すでにを避けて、関数が呼び出されたときに既にエントリがある場合は避け、swap!を入力した後にすでにエントリがある場合はassocを避けて "double checkする必要があります"あなたがswap!を開始する前に(ただし、あなたがに決めた後に)他のスレッドが忍び寄っている可能性があるので、地図には現在のトークンのエントリがありません。)、現在のトークンの値を割り当てます。その場合、新しいトークンを作成する代わりにその割り当てを尊重する必要があります。

編集:もちろん、Javaのバージョンではスレッドセーフではない問題があります。デフォルトではJavaのすべてが変更可能でスレッドセーフではないからです。少なくともClojureでは、あなたはをそこに置く必要があります。「はい、これは危険です、私は何をしているのか知っています。

したがって、いくつかの意味で、Ankurのソリューションはであり、Javaコードの翻訳の完全な翻訳ですが、それを改善することさえもより良いでしょう!

+0

私は、事態を悪化させると思っていたメモについては何か不思議です。私は簡単でしたが、私の主張は、ここで原子を無条件に再結合することは安全ではないということでした。なぜなら、2つのレーシングスレッドがそうしているので、他のレーシングスレッドが行った変更を取り除くからです。これらの2つのレーシングスレッドをどのように調整するかはデザインの問題です。今日の 'swap!'のドキュメントを読み返してみると、値が一貫して設定されるまで、内部的にスピンしているので、 'compare-and-set! 'を使って私の提案は不合理です。私はまだ* ref *に対して 'dosync'を使うことが最も明確な方法だと考えています。 – seh

+0

'(swap!a f)'に競争する2つのスレッドは決して変更を捨てません。アトムは常に整合した状態になり、最終的に 'a'は'(f(f old-a)) 'に設定されます。問題は時代遅れかもしれない 'swap! 'の* outside *からの情報に基づいて' f1'と 'f2'を構築しているということです。 'compare-and-swap! 'はこの間違いを簡単に*するだけです。 'dosync'は原子よりむしろrefであることを確認していれば問題ありませんが、通常は単一の参照を管理するのは残念ですが、良い' swap! 'を書く方法を知ることは重要です。 – amalloy

0

シングルマップは十分でしょう。

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

あなたは基本的にClojureのにJavaの不可欠コードをマップしようとしましたが、あなたは、あなたの質問にその解決策を得た理由thatsの。段階的な命令的スタイルを考えずに、表現を構成するという観点から考えてみてください。

+0

'compare-and-set! 'ではなく' swap!'の使用を提案しましたか、あるいは 'dosync'呼び出しの中でマップを再バインドしたのですか?もしあなたが他のスレッドと競合することを期待しているなら、ここで 'swap! 'を使って変更を上書きすることができます。 – seh

関連する問題