2

次のようなマップのリストがあります。

(def balances ({:name "Steve" :money 1000} {:name "Bill" :money 1000} ...))

スティーブのお金の特定の金額 (100 としましょう) をビルに転送し、データ構造を更新する関数を作成しようとしています。

({:name "Steve" :money 900} {:name "Bill" :money 1100})

関数は毎回パラメーターとしてバランスを期待する必要があり、次のようになると考えました。

(defn transfer [current-balances amount sender receiver] ... )

このような関数はどのように見えますか?また、これは口座残高を管理および更新する賢い方法ですか? 一般に、私のプログラムはかなり長い送金リストを取り、それらを残高構造に繰り返し適用します。Clojureの永続的なデータ構造のために、常にバランス構造を伝達関数に渡す必要がありますか?

4

3 に答える 3

3

残高がすべて単一の包括的なデータ構造に含まれており、その構造が適切な値であるという事実により、転送関数は、口座の現在の状態を記述する構造と、変化を記述する別の構造を単純に取り込み、新しい状態を生成することができます。アカウントの。これにより、転送アクションを適切な値として扱うことができ、転送の非常に長いリストを処理するのに役立ちます:)私の唯一の変更は、リストの代わりに残高のマップを使用することです.

bar> (def balances {"Steve" {:money 1000} "Bill" {:money 1000}})
#'bar/balances

bar> (def transfers [["Steve" "Bill" 100] ["Bill" "Steve" 100] 
                     ["Steve" "Bill" 10 ] ["Bill" "Steve" 10 ] 
                     ["Bill" "Steve" 10 ]])
#'bar/transfers

次に、これらのいずれかを取得してアカウントに適用する単純な伝達関数を定義します

(defn transfer [balances [from to ammount]] 
  (-> balances 
      (update-in [from :money] - ammount) 
      (update-in [to   :money] + ammount)))

この関数は、すべてのアカウントの状態への転送のシーケンスを直接削減するために使用できます。

bar> (reduce transfer balances transfers)
{"Bill" {:money 990}, "Steve" {:money 1010}}

顧客からの新しい送金を受け入れる関数は、この関数を使用して、銀行を保存するために選択したもの (DB、atom、エージェントなど) の状態を変更できます。

bar> (def bank (agent {:current balances :ledger []}))
#'bar/bank

bar> (defn accept-transfers [transfers] 
       (send bank assoc :current (reduce transfer (:current @bank) transfers) 
                        :ledger (concat transfers (:ledger @bank))))
#'bar/accept-transfers

bar> (accept-transfers transfers)
#<Agent@2eb9bc1: {:current {"Bill" {:money 1000}, "Steve" {:money 1000}}, :ledger []}>

これにより、銀行のキューに送金が置かれます (そして、送金が実行されている間に REPL が迅速に出力するエージェントを返します) 銀行を見ると、これらすべての送金が適用されていることがわかります。

bar> bank
#<Agent@2eb9bc1: {:current {"Bill" {:money 990}, "Steve" {:money 1010}}, 
                  :ledger (["Steve" "Bill" 100] ["Bill" "Steve" 100] 
                           ["Steve" "Bill" 10] ["Bill" "Steve" 10] 
                           ["Bill" "Steve" 10])}>
于 2012-12-31T18:48:25.833 に答える
1

Clojureデータは不変であり、変更することはできません。STMを使用する必要があります。

これがサンプルコードです(質問のポイントまで簡略化されています)。

(def account1 {:name "Foo" :money (ref 1000)})
(def account2 {:name "Bar" :money (ref 1000)})

(defn transfer
  [from to amount]
  (dosync
     (alter (:money from) - amount)
     (alter (:money to) + amount)))

(transfer account1 account2 100)

(println @(:money account1))
(println @(:money account2))

詳細については、 http://clojure.org/refs、http://clojure.org/atoms、およびおそらくhttp://clojure.org/agentsを参照してください

于 2012-12-31T13:46:54.980 に答える
0

これが私の2セントです。それもお役に立てば幸いです。

(def balances {1 (ref {:name "Steve"
                       :money 1000})
               2 (ref {:name "Bill"
                       :money 1000})
               3 (ref {:name "John"
                       :money 1000})})

(defn balance [person-id]
  ((deref (balances person-id)) :money))

(defn credit [balance amount]
  (- balance amount))

(defn debet [balance amount]
  (+ balance amount))

(defn update-account [person-id operation-fn amount]
  (alter (get balances person-id)
         #(assoc % :money
                 (operation-fn (:money %) amount))))

(defn credit-account [person-id amount]
  (update-account person-id credit amount))

(defn debet-account [person-id amount]
  (update-account person-id debet amount))

(defn transfer [sender-id receiver-id amount]
  (if (< (credit (balance sender-id) amount) 0)
    {:result :insufficient-fund}
    (do (credit-account sender-id amount)
        (debet-account receiver-id amount)
        {:result :ok})))

テスト

(defn transaction []
  (dosync (transfer 1 2 100)
          (transfer 2 3 200)
          (transfer 3 1 200)))

(transaction)
-> {:result :ok}

balances
-> {1 #<Ref@b54dba: {:money 1100, :name "Steve"}>,
    2 #<Ref@1020230: {:money 900, :name "Bill"}>,
    3 #<Ref@1803641: {:money 1000, :name "John"}>}
于 2012-12-31T21:24:46.520 に答える