更新列表中包含的某些映射值

时间:2012-12-31 11:44:45

标签: clojure

我有一个地图列表,看起来像这样:

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

我正在尝试编写一个函数,将给定数量(比方说100)的Steves钱转移到Bill并更新数据结构:

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

我认为该函数每次都应该将余额作为参数,看起来像这样:

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

这样的功能看起来如何,这是一种管理和更新帐户余额的聪明方法?一般来说,我的程序会占用很长的转移列表,并反复将它们应用于余额结构。我是否总是必须将平衡结构传递给传输功能,因为 Clojure 中的持久数据结构?

3 个答案:

答案 0 :(得分: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,原子,代理等)

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])}>

答案 1 :(得分: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/refshttp://clojure.org/atoms以及http://clojure.org/agents

了解详情

答案 2 :(得分:0)

这是我的两分钱。我希望它也会有所帮助。

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