【问题标题】:Idiomatic way to transform a growing vector in Clojure在 Clojure 中转换不断增长的向量的惯用方法
【发布时间】:2016-10-05 04:52:17
【问题描述】:

假设我在 Clojure 中有一个向量:

(def myvec (atom (vector 1)))

我有这个函数可以为向量添加新值:

(defn inc-myvec! []
  (swap! myvec conj (inc (last @myvec))))

假设在某个触发器上不时调用此函数。让我们触发几次。

(dotimes [i 5] (inc-myvec!))
@myvec
;=> [1 2 3 4 5 6]

现在,如果我想从该向量中的每个元素中减去 1,我会这样做:

(def myvec2 (atom (mapv dec @myvec)))
@myvec2
;=> [0 1 2 3 4 5]

这当然不是很好,因为我必须在每次更改 myvec 时都这样做。这也很浪费,因为它在每次更改时都会处理整个向量。我们可以做得更好。让我们实现观察者模式并只计算最后一个附加的值:

(defn watcher [_ _ _ new-myvec]
  (swap! myvec2 conj (dec (last new-myvec))))
(add-watch myvec :watcher watcher)

这还不错。但理想情况下,我希望观察者在另一个线程中操作初学者。是否有更好、更惯用的方法来处理这种转换,您基本上需要在传入数据流到达时对其做出反应并将其转换为其他东西?

【问题讨论】:

  • 我不确定您要在这里改进什么。你能制定一般用例吗?有用于操作流的库,但您的示例几乎不是关于流恕我直言(为什么是向量,为什么是原子?)。如果我要优化您的示例,我会使用自定义的“范围”数据结构(与整数的 2 元组一样轻量级)。
  • @ValentinWaeselynck 一般用例是根据另一个向量计算一个向量。向量不是示例的重要部分,可以是地图或列表。我正在使用加法和减法,但在现实生活中,这将是相对密集的计算。因此,我想在另一个线程中进行第二次操作(这就是我使用原子的原因),而对于手表来说这是不可能的(尽管我可以在手表内部分叉未来)。我不确定这是什么困扰着我;我想我可以接受人们告诉我这没关系,但不知何故,我希望听到“不傻,这不是它的完成方式”。 :)
  • 一般用例真的是关于基于整个其他集合计算整个集合吗? (非常不像流)因为在您的最后一个改进中,您似乎可以只更新最后一个值。很抱歉坚持,但通常答案在于更好地提出问题:)
  • @ValentinWaeselynck - 当然!我正在处理的输入集合是无穷无尽的,读取速度相对较快,最多每秒几次。输出收集基于输入值,但在某个时期(每分钟不少于一个值)。想法是在输入进入时计算输出。我计算整个事情的例子只是一个起点,最后一个例子是我正在寻找的更多。我最多使用来自集合尾部的几个值或输入中的最后一个值来计算输出中的最新值。
  • 这绝对是流 - 检查核心异步并摆脱那些原子 :)

标签: clojure pipeline idioms


【解决方案1】:

如果您对一般用例进行更多解释,可能更容易为您指明正确的方向。你真的需要一个向量作为输出(即随机访问能力)吗?还是您只是想要一种方法来转换来自一个来源的数据并将其放在其他地方?

如果您实际上不需要矢量的随机访问,我会说使用core.async 和通道是最好的方法。通过这种方式,您可以让特定的线程或循环处理数据发布、转换和消费的每个步骤,只需在它们之间放置一个通道。

这里有一些关于 core.async 的好文章可以从中学习(但如果你快速 google 一下,还有更多的文章):

希望对您有所帮助!

【讨论】:

  • 我不一定需要向量;根据数据和特定情况,我的收藏实际上更有可能是地图或列表。但在所有情况下,我只关注尾部值,因此渠道非常相关,我肯定会考虑它们。
猜你喜欢
  • 1970-01-01
  • 2011-05-04
  • 2013-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-19
  • 2016-12-08
相关资源
最近更新 更多