Clojure 通过改变状态转换序列的功能性/克隆性方法是什么?

Clojure 通过改变状态转换序列的功能性/克隆性方法是什么?,clojure,Clojure,问题背景与股票交易有关。我正试图在出售股票时更新特定股票的持有量。简化摘录 ;; @holdings - an atom { "STOCK1" {:trades [Trade#{:id 100 :qty 50}, Trade#{ :id 140 :qty 50}]} "STOCK2" ... } 现在给出一个交易{:ID200:StockStock1,:qty 75}的销售交易,我希望持有量能够反映 { "STOCK1" {:tra

问题背景与股票交易有关。我正试图在出售股票时更新特定股票的持有量。简化摘录

;; @holdings - an atom
{ "STOCK1" {:trades [Trade#{:id 100 :qty 50}, Trade#{ :id 140 :qty 50}]}
 "STOCK2" ... }
现在给出一个交易{:ID200:StockStock1,:qty 75}的销售交易,我希望持有量能够反映

{ "STOCK1" {:trades [Trade#{:id 100 :qty 0}, Trade#{ :id 140 :qty 25}]} }
;; or better drop the records with zero qty.
{ "STOCK1" {:trades [Trade#{ :id 140 :qty 25}]} }
我找不到功能性的答案。。我所能看到的是一个doseq循环,其中的原子持有类似于销售数量的状态,可以通过1次或n次交易来满足,但感觉就像Clojure中的C

有没有更符合clojure的解决方案?映射看起来不合适,因为每个记录处理都需要更新外部状态待定销售数量75->25->0


免责声明:Clojure新手,他想学习。

与命令式编程不同,命令式编程通常会就地修改值,而在函数式编程中,您会创建包含修改的新值。因此,您必须使用更新创建一个新版本的地图,其中包含一个与您的交易相关的修改向量。大概是这样的:

(require '[com.rpl.specter :as s])


(let [stocks     {"STOCK1" {:trades [{:trade/id 100 :trade/qty 50}, {:trade/id 140 :trade/qty 50}]}}
      sale-trade {:trade/id 200 :trade/stock "STOCK1" :trade/qty 75}
      trade-path [(s/keypath (:trade/stock sale-trade) :trades) s/ALL]
      qty-path   (conj trade-path :trade/qty)
      [new-qty _] (reduce (fn [[new-amounts leftover] v]
                              (let [due-amount (min v leftover)]
                                  [(conj new-amounts (- v due-amount)) (- leftover due-amount)]))
                          [[] (:trade/qty sale-trade)]
                          (s/select qty-path stocks))]
    (->> stocks
         (s/setval (s/subselect qty-path) new-qty)
         (s/setval [trade-path #(zero? (:trade/qty %))] s/NONE)))

=> {"STOCK1" {:trades [#:trade{:id 140, :qty 25}]}}
(def conj-positive-trade ((filter (comp pos? :qty)) conj))

(defn sell [trades sale]
  (update-in trades
             [(:stock sale) :trades]
             #(first
               (reduce (fn [[dst remaining] {:keys [qty id]}]
                         (let [diff (- qty remaining)]
                           [(conj-positive-trade dst {:id id :qty diff})
                            (max 0 (- diff))]))
                       [[] (:qty sale)]
                       %))))
这里,conj-positive-trade是一个函数,它只将positive-trade连接到一个向量

以下是如何使用销售功能:

(sell {"STOCK1" {:trades [{:id 100 :qty 50} {:id 140 :qty 50} {:id 150 :qty 70}]}}
      {:id 200 :stock "STOCK1", :qty 75})
;; => {"STOCK1" {:trades [{:id 140, :qty 25} {:id 150, :qty 70}]}}

与命令式编程不同,在命令式编程中,您经常就地修改值,而在函数式编程中,您会创建包含修改的新值。因此,您必须使用更新创建一个新版本的地图,其中包含一个与您的交易相关的修改向量。大概是这样的:

(def conj-positive-trade ((filter (comp pos? :qty)) conj))

(defn sell [trades sale]
  (update-in trades
             [(:stock sale) :trades]
             #(first
               (reduce (fn [[dst remaining] {:keys [qty id]}]
                         (let [diff (- qty remaining)]
                           [(conj-positive-trade dst {:id id :qty diff})
                            (max 0 (- diff))]))
                       [[] (:qty sale)]
                       %))))
这里,conj-positive-trade是一个函数,它只将positive-trade连接到一个向量

以下是如何使用销售功能:

(sell {"STOCK1" {:trades [{:id 100 :qty 50} {:id 140 :qty 50} {:id 150 :qty 70}]}}
      {:id 200 :stock "STOCK1", :qty 75})
;; => {"STOCK1" {:trades [{:id 140, :qty 25} {:id 150, :qty 70}]}}

作为一个替代解决方案,它不会使用specter,这很好,但需要购买。我会保留两个原子,一个是所有交易的原始列表,一个是你刚刚连接到的映射向量,例如{:trade id 1:name AAPL:price 100:qty 20}],另一个是由股票名称索引的映射的映射。您可以通过分组或筛选从一个到另一个,因此,如果您添加了AAPL交易,您可以按如下方式更新数量: 交换[AAPL]->@listing filter=:name%AAPL映射中的分组结果更新:数量减少+


当涉及到您保留的交易id时,它会稍微复杂一些,因为当您考虑PnL时,可能会考虑FIFO或LIFO,但同样,您可以使用reduce或reduced来停止您想要的操作。

作为一种替代解决方案,它不会使用specter,这很好,但需要购买。我会保留两个原子,一个是所有交易的原始列表,一个是你刚刚连接到的映射向量,例如{:trade id 1:name AAPL:price 100:qty 20}],另一个是由股票名称索引的映射的映射。您可以通过分组或筛选从一个到另一个,因此,如果您添加了AAPL交易,您可以按如下方式更新数量: 交换[AAPL]->@listing filter=:name%AAPL映射中的分组结果更新:数量减少+


当涉及到您保留的交易id时,它会稍微复杂一些,因为当您考虑PnL时,可能会有FIFO或LIFO考虑因素-但同样,您可以使用reduces或reduced来停止您想要的操作。

我可能会首先找出核心库中缺少的基本功能的哪一部分。在您的例子中,它是在保持某些变化状态的同时映射集合的函数

可以这样看:

(defn map-state [f state data]
  (when-let [[x & xs] (seq data)]
    (lazy-seq
     (let [[new-state new-x] (f state x)]
       (cons new-x (map-state f new-state xs))))))
它如何在像您这样的环境中工作的小示例:

(def running-subtract (partial map-state
                               #(let [qty (min %1 %2)]
                                  [(- %1 qty) (- %2 qty)])))
#'user/running-subtract

user> (running-subtract 10 (range 7))
;;=> (0 0 0 0 0 5 6)
因此,您可以使用它从您的交易中减去状态:

(defn running-decrease-trades [trades amount]
  (map-state (fn [amount trade]
               (let [sub (min (:qty trade) amount)]
                 [(- amount sub) (update trade :qty - sub)]))
             amount
             trades))
使用此功能转换数据将非常简单,如下所示:

(defn handle-trade [data {:keys [stock qty]}]
  (update-in data [stock :trades] running-decrease-trades qty))


user> (handle-trade
       {"STOCK1" {:trades [{:id 100, :qty 50} {:id 140, :qty 50}]}}
       {:stock "STOCK1" :qty 75})
{"STOCK1" {:trades ({:id 100, :qty 0} {:id 140, :qty 25})}}

虽然我非常喜欢specter,但我想说这是一种过分的做法。

我可能会首先找出核心库中缺少哪些基本功能。在您的例子中,它是在保持某些变化状态的同时映射集合的函数

可以这样看:

(defn map-state [f state data]
  (when-let [[x & xs] (seq data)]
    (lazy-seq
     (let [[new-state new-x] (f state x)]
       (cons new-x (map-state f new-state xs))))))
它如何在像您这样的环境中工作的小示例:

(def running-subtract (partial map-state
                               #(let [qty (min %1 %2)]
                                  [(- %1 qty) (- %2 qty)])))
#'user/running-subtract

user> (running-subtract 10 (range 7))
;;=> (0 0 0 0 0 5 6)
因此,您可以使用它从您的交易中减去状态:

(defn running-decrease-trades [trades amount]
  (map-state (fn [amount trade]
               (let [sub (min (:qty trade) amount)]
                 [(- amount sub) (update trade :qty - sub)]))
             amount
             trades))
使用此功能转换数据将非常简单,如下所示:

(defn handle-trade [data {:keys [stock qty]}]
  (update-in data [stock :trades] running-decrease-trades qty))


user> (handle-trade
       {"STOCK1" {:trades [{:id 100, :qty 50} {:id 140, :qty 50}]}}
       {:stock "STOCK1" :qty 75})
{"STOCK1" {:trades ({:id 100, :qty 0} {:id 140, :qty 25})}}

尽管我非常喜欢specter,但我想说这是一种过分的杀伤力。

每当你想在Clojure中查看序列/集合,同时传递一些附加状态时,想想reduce reduce就像一把瑞士军刀,例如map和filter都可以用reduce实现。但是如何在一个归约函数中存储多个状态呢?您只需使用映射作为累加器

让我把你的问题提炼一下。让我们创建一个只处理一个问题的函数

从中减去 给定一系列数字'values',从每个数字中减去数字'value' 在'values'中,直到整个'value'被减除。返回一个包含2个键的映射,:result contains 减去的值和的向量:rem保留余数。 [价值观] 减少fn[{:keys[r] em]:结果是}n] 如果零?雷姆 更新结果:结果conj n 让[sub min rem n res-n sub rem数学/abs-子rem] ->结果 更新:result conj res 助理:rem {:rem值:结果[]} 价值观 ;; 当值小于所有值之和时,余数为0 从[100 200 300 400]500中减去 ;; => {:rem 0,:result[0 100 400]} ;; 当值大于所有值之和时,余数大于0 从[100 200 300 400]1200中减去 ;; => {:rem 200,:result[0]} 现在我们可以用这个函数来卖股票了。注意,map可以接受多个集合/序列作为参数

def库存 atom{STOCK1{:trades[{:ID100:qty 50}{:ID140:qty 50}} defn sell[stocks{:key[id stock qty]}] 让[交易]进入股票[股票:交易] qtys地图:交易数量 新qtys:从qtys数量中减去结果] 地图fn[交易数量] 关联交易:数量 交易 新QTY 销售@stocks{:id 300:qty 75:stockstock1} ;; => {:ID100,数量0}{:ID140,数量25}
每当您想在Clojure中查看序列/集合时,在传递一些附加状态的同时,想想reduce reduce就像一把瑞士军刀,例如map和filter都可以用reduce实现。但是如何在一个归约函数中存储多个状态呢?您只需使用映射作为累加器

让我把你的问题提炼一下。让我们创建一个只处理一个问题的函数

从中减去 给定一系列数字'values',从每个数字中减去数字'value' 在'values'中,直到整个'value'被减除。返回一个包含2个键的映射,:result contains 减去的值和的向量:rem保留余数。 [价值观] 减少fn[{:keys[rem]:结果}n] 如果零?雷姆 更新结果:结果conj n 让[sub min rem n res-n sub rem数学/abs-子rem] ->结果 更新:result conj res 助理:rem {:rem值:结果[]} 价值观 ;; 当值小于所有值之和时,余数为0 从[100 200 300 400]500中减去 ;; => {:rem 0,:result[0 100 400]} ;; 当值大于所有值之和时,余数大于0 从[100 200 300 400]1200中减去 ;; => {:rem 200,:result[0]} 现在我们可以用这个函数来卖股票了。注意,map可以接受多个集合/序列作为参数

def库存 atom{STOCK1{:trades[{:ID100:qty 50}{:ID140:qty 50}} defn sell[stocks{:key[id stock qty]}] 让[交易]进入股票[股票:交易] qtys地图:交易数量 新qtys:从qtys数量中减去结果] 地图fn[交易数量] 关联交易:数量 交易 新QTY 销售@stocks{:id 300:qty 75:stockstock1} ;; => {:ID100,数量0}{:ID140,数量25}
啊。。丢失的一块。看看specter。关于如何轻松修改嵌套数据结构这个老问题,这应该是公认的答案。幽灵看起来很可怕。是我无法正确地陈述问题。现在我已经得到了帮助,问题是如何减少一个序列和一些状态,这些状态因项目而异。啊。。丢失的一块。看看specter。关于如何轻松修改嵌套数据结构这个老问题,这应该是公认的答案。幽灵看起来很可怕。是我无法正确地陈述问题。现在我已经得到了帮助-问题是如何减少一个序列与一些状态的关联,这些状态随着项目的不同而变化。谢谢你上了这堂让我大开眼界的课-就像你说的大脑块正在绘制一张状态变化的地图。更好的是,它是在没有外部库的情况下完成的。一个全新的定义:defn map state[f state data]->>数据缩减f first%1%2[state]rest map second感谢您提供了这个令人大开眼界的课程-就像您所说的,大脑块正在使用不断变化的状态进行映射。更好的是,它是在没有外部库的情况下完成的。一个全新的定义:defn map state[f state data]->>数据缩减f first%1%2[state]rest map secondNice!reduce确实出现在我的脑海中,但我想我过早地放弃了它。我在使用@leetwinski的地图和州方法。这种方法帮助我解决了下一个难题,那就是在每次销售中生成损益条目列表。使用状态映射{:sale{}:updated holdings[]:gains[]}和reduce操作。很好!reduce确实出现在我的脑海中,但我想我过早地放弃了它。我在使用@leetwinski的地图和州方法。这种方法帮助我解决了下一个难题,那就是在每次销售中生成损益条目列表。使用了状态映射{:sale{}:updated holdin gs[]:通过reduce操作获得[]}。