Clojure 中的复杂数据操作
Complex data manipulation in Clojure
我正在进行个人市场分析项目。我有一个数据结构代表市场上所有最近的转折点,看起来像这样:
[{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}
{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}
{:high 1.12215, :time "2016-08-02T23:00:00.000000Z"}
{:high 1.12273, :time "2016-08-02T21:15:00.000000Z"}
{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.119215, :time "2016-08-02T12:30:00.000000Z"}
{:low 1.118755, :time "2016-08-02T12:00:00.000000Z"}
{:low 1.117575, :time "2016-08-02T06:00:00.000000Z"}
{:low 1.117135, :time "2016-08-02T04:30:00.000000Z"}
{:low 1.11624, :time "2016-08-02T02:00:00.000000Z"}
{:low 1.115895, :time "2016-08-01T21:30:00.000000Z"}
{:low 1.11552, :time "2016-08-01T11:45:00.000000Z"}
{:low 1.11049, :time "2016-07-29T12:15:00.000000Z"}
{:low 1.108825, :time "2016-07-29T08:30:00.000000Z"}
{:low 1.10839, :time "2016-07-29T08:00:00.000000Z"}
{:low 1.10744, :time "2016-07-29T05:45:00.000000Z"}
{:low 1.10716, :time "2016-07-28T19:30:00.000000Z"}
{:low 1.10705, :time "2016-07-28T18:45:00.000000Z"}
{:low 1.106875, :time "2016-07-28T18:00:00.000000Z"}
{:low 1.10641, :time "2016-07-28T05:45:00.000000Z"}
{:low 1.10591, :time "2016-07-28T01:45:00.000000Z"}
{:low 1.10579, :time "2016-07-27T23:15:00.000000Z"}
{:low 1.105275, :time "2016-07-27T22:00:00.000000Z"}
{:low 1.096135, :time "2016-07-27T18:00:00.000000Z"}]
从概念上讲,我想匹配 :high
/:low
对,计算出价格范围(高-低)和中点(高和低的平均值),但我没有希望生成所有可能的对。
我想做的是从集合 {:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
中的第一项开始,遍历集合的剩余部分 "down",为每个 :low
项创建一对直到我点击了下一个 :high
项。一旦我点击了下一个 :high
项,我就不再对任何其他对感兴趣了。在这种情况下,只创建了一对,即 :high
和第一个 :low
- 我停在那里,因为下一个(第 3 个)项目是 :high
。 1 生成的记录应该类似于 {:price-range 0.000365, :midpoint 1.121272, :extremes [{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}]}
接下来,我将移动到集合 {:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
中的第 2 项,然后遍历 "down" 集合的其余部分,与每个 :high
项创建一对,直到我点击下一个 :low
项。在这种情况下,我生成了 5 个新记录,即 :low
和接下来的 5 个 :high
项,它们都是连续的;这 5 条记录中的第一个看起来像
{:price-range 0.000064, :midpoint 1.12131, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}]}
这 5 条记录中的第二条看起来像
{:price-range 0.000835, :midpoint 1.1215075, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}]}
等等。
在那之后,我得到了一个 :low
所以我停在那里。
然后我将移动到第 3 个项目 {:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}
并走 "down" 与每个 :low
创建对,直到我击中下一个 :high
。在这种情况下,我生成了 0 对,因为 :high
后面紧跟着另一个 :high
。接下来的 3 个 :high 项目也一样,紧接着是另一个 :high
接下来我得到第 7 个项目 {:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
,它应该与以下 20 个 :low
项目中的每一个生成一对。
我生成的结果将是创建的所有对的列表:
[{:price-range 0.000365, :midpoint 1.121272, :extremes [{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}]}
{:price-range 0.000064, :midpoint 1.12131, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}]}
...
如果我使用 Python 之类的东西来实现它,我可能会使用几个嵌套循环,当我不再看到 [=14= 时,使用 break
退出内部循环]s 与我的 :low
配对,反之亦然,并在我遍历 2 个循环时将所有生成的记录累积到一个数组中。我只是想不出使用 Clojure 攻击它的好方法...
有什么想法吗?
首先,您可以按以下方式重新表述:
- 你必须找到所有的边界点,其中
:high
后面跟着 :low
,反之亦然
- 你需要在 绑定之前 获取项目,并用它和绑定后的每个项目制作一些东西,但直到下一个切换绑定。
为简单起见,我们使用以下数据模型:
(def data0 [{:a 1} {:b 2} {:b 3} {:b 4} {:a 5} {:a 6} {:a 7}])
第一部分可以通过使用 partition-by
函数来实现,每次函数更改其对已处理项目的值时都会拆分输入集合:
user> (def step1 (partition-by (comp boolean :a) data0))
#'user/step1
user> step1
(({:a 1}) ({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7}))
现在你需要把这些组中的每两个都拿走并操纵它们。这些组应该是这样的:
[({:a 1}) ({:b 2} {:b 3} {:b 4})]
[({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7})]
这是通过partition
函数实现的:
user> (def step2 (partition 2 1 step1))
#'user/step2
user> step2
((({:a 1}) ({:b 2} {:b 3} {:b 4}))
(({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7})))
你必须为每一对小组做点什么。你可以用 map:
user> (def step3 (map (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))
step2))
#'user/step3
user> step3
(([{:a 1} {:b 2}] [{:a 1} {:b 3}] [{:a 1} {:b 4}])
([{:b 4} {:a 5}] [{:b 4} {:a 6}] [{:b 4} {:a 7}]))
但是由于您需要串联列表而不是分组列表,因此您需要使用 mapcat
而不是 map
:
user> (def step3 (mapcat (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))
step2))
#'user/step3
user> step3
([{:a 1} {:b 2}]
[{:a 1} {:b 3}]
[{:a 1} {:b 4}]
[{:b 4} {:a 5}]
[{:b 4} {:a 6}]
[{:b 4} {:a 7}])
这就是我们想要的结果(几乎就是这样,因为我们只是生成矢量,而不是地图)。
现在您可以使用线程宏对其进行美化:
(->> data0
(partition-by (comp boolean :a))
(partition 2 1)
(mapcat (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))))
这给你完全相同的结果。
应用于您的数据,它看起来几乎相同(另一个结果生成 fn)
user> (defn hi-or-lo [item]
(item :high (item :low)))
#'user/hi-or-lo
user>
(->> data
(partition-by (comp boolean :high))
(partition 2 1)
(mapcat (fn [[lbounds rbounds]]
(let [left-bound (last lbounds)
left-val (hi-or-lo left-bound)]
(map #(let [right-val (hi-or-lo %)
diff (Math/abs (- right-val left-val))]
{:extremes [left-bound %]
:price-range diff
:midpoint (+ (min right-val left-val)
(/ diff 2))})
rbounds))))
(clojure.pprint/pprint))
它打印以下内容:
({:extremes
[{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}],
:price-range 3.6500000000017074E-4,
:midpoint 1.1212725}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}],
:price-range 6.399999999999739E-4,
:midpoint 1.12141}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}],
:price-range 8.350000000001412E-4,
:midpoint 1.1215074999999999}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12215, :time "2016-08-02T23:00:00.000000Z"}],
:price-range 0.001060000000000061,
:midpoint 1.12162}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12273, :time "2016-08-02T21:15:00.000000Z"}],
:price-range 0.0016400000000000858,
:midpoint 1.12191}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}],
:price-range 0.0022900000000001253,
:midpoint 1.1222349999999999}
{:extremes
[{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.119215, :time "2016-08-02T12:30:00.000000Z"}],
:price-range 0.004164999999999974,
:midpoint 1.1212975}
{:extremes
[{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.118755, :time "2016-08-02T12:00:00.000000Z"}],
:price-range 0.004625000000000101,
:midpoint 1.1210675}
...
作为关于 "complex data manipulation" 问题的答案,我建议您从 clojure 核心查看所有集合的操作函数,然后尝试将任何任务分解为这些函数的应用程序。需要超越它们的情况并不多见。
我正在进行个人市场分析项目。我有一个数据结构代表市场上所有最近的转折点,看起来像这样:
[{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}
{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}
{:high 1.12215, :time "2016-08-02T23:00:00.000000Z"}
{:high 1.12273, :time "2016-08-02T21:15:00.000000Z"}
{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.119215, :time "2016-08-02T12:30:00.000000Z"}
{:low 1.118755, :time "2016-08-02T12:00:00.000000Z"}
{:low 1.117575, :time "2016-08-02T06:00:00.000000Z"}
{:low 1.117135, :time "2016-08-02T04:30:00.000000Z"}
{:low 1.11624, :time "2016-08-02T02:00:00.000000Z"}
{:low 1.115895, :time "2016-08-01T21:30:00.000000Z"}
{:low 1.11552, :time "2016-08-01T11:45:00.000000Z"}
{:low 1.11049, :time "2016-07-29T12:15:00.000000Z"}
{:low 1.108825, :time "2016-07-29T08:30:00.000000Z"}
{:low 1.10839, :time "2016-07-29T08:00:00.000000Z"}
{:low 1.10744, :time "2016-07-29T05:45:00.000000Z"}
{:low 1.10716, :time "2016-07-28T19:30:00.000000Z"}
{:low 1.10705, :time "2016-07-28T18:45:00.000000Z"}
{:low 1.106875, :time "2016-07-28T18:00:00.000000Z"}
{:low 1.10641, :time "2016-07-28T05:45:00.000000Z"}
{:low 1.10591, :time "2016-07-28T01:45:00.000000Z"}
{:low 1.10579, :time "2016-07-27T23:15:00.000000Z"}
{:low 1.105275, :time "2016-07-27T22:00:00.000000Z"}
{:low 1.096135, :time "2016-07-27T18:00:00.000000Z"}]
从概念上讲,我想匹配 :high
/:low
对,计算出价格范围(高-低)和中点(高和低的平均值),但我没有希望生成所有可能的对。
我想做的是从集合 {:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
中的第一项开始,遍历集合的剩余部分 "down",为每个 :low
项创建一对直到我点击了下一个 :high
项。一旦我点击了下一个 :high
项,我就不再对任何其他对感兴趣了。在这种情况下,只创建了一对,即 :high
和第一个 :low
- 我停在那里,因为下一个(第 3 个)项目是 :high
。 1 生成的记录应该类似于 {:price-range 0.000365, :midpoint 1.121272, :extremes [{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}]}
接下来,我将移动到集合 {:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
中的第 2 项,然后遍历 "down" 集合的其余部分,与每个 :high
项创建一对,直到我点击下一个 :low
项。在这种情况下,我生成了 5 个新记录,即 :low
和接下来的 5 个 :high
项,它们都是连续的;这 5 条记录中的第一个看起来像
{:price-range 0.000064, :midpoint 1.12131, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}]}
这 5 条记录中的第二条看起来像
{:price-range 0.000835, :midpoint 1.1215075, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}]}
等等。
在那之后,我得到了一个 :low
所以我停在那里。
然后我将移动到第 3 个项目 {:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}
并走 "down" 与每个 :low
创建对,直到我击中下一个 :high
。在这种情况下,我生成了 0 对,因为 :high
后面紧跟着另一个 :high
。接下来的 3 个 :high 项目也一样,紧接着是另一个 :high
接下来我得到第 7 个项目 {:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
,它应该与以下 20 个 :low
项目中的每一个生成一对。
我生成的结果将是创建的所有对的列表:
[{:price-range 0.000365, :midpoint 1.121272, :extremes [{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}]}
{:price-range 0.000064, :midpoint 1.12131, :extremes [{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}]}
...
如果我使用 Python 之类的东西来实现它,我可能会使用几个嵌套循环,当我不再看到 [=14= 时,使用 break
退出内部循环]s 与我的 :low
配对,反之亦然,并在我遍历 2 个循环时将所有生成的记录累积到一个数组中。我只是想不出使用 Clojure 攻击它的好方法...
有什么想法吗?
首先,您可以按以下方式重新表述:
- 你必须找到所有的边界点,其中
:high
后面跟着:low
,反之亦然 - 你需要在 绑定之前 获取项目,并用它和绑定后的每个项目制作一些东西,但直到下一个切换绑定。
为简单起见,我们使用以下数据模型:
(def data0 [{:a 1} {:b 2} {:b 3} {:b 4} {:a 5} {:a 6} {:a 7}])
第一部分可以通过使用 partition-by
函数来实现,每次函数更改其对已处理项目的值时都会拆分输入集合:
user> (def step1 (partition-by (comp boolean :a) data0))
#'user/step1
user> step1
(({:a 1}) ({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7}))
现在你需要把这些组中的每两个都拿走并操纵它们。这些组应该是这样的: [({:a 1}) ({:b 2} {:b 3} {:b 4})] [({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7})]
这是通过partition
函数实现的:
user> (def step2 (partition 2 1 step1))
#'user/step2
user> step2
((({:a 1}) ({:b 2} {:b 3} {:b 4}))
(({:b 2} {:b 3} {:b 4}) ({:a 5} {:a 6} {:a 7})))
你必须为每一对小组做点什么。你可以用 map:
user> (def step3 (map (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))
step2))
#'user/step3
user> step3
(([{:a 1} {:b 2}] [{:a 1} {:b 3}] [{:a 1} {:b 4}])
([{:b 4} {:a 5}] [{:b 4} {:a 6}] [{:b 4} {:a 7}]))
但是由于您需要串联列表而不是分组列表,因此您需要使用 mapcat
而不是 map
:
user> (def step3 (mapcat (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))
step2))
#'user/step3
user> step3
([{:a 1} {:b 2}]
[{:a 1} {:b 3}]
[{:a 1} {:b 4}]
[{:b 4} {:a 5}]
[{:b 4} {:a 6}]
[{:b 4} {:a 7}])
这就是我们想要的结果(几乎就是这样,因为我们只是生成矢量,而不是地图)。
现在您可以使用线程宏对其进行美化:
(->> data0
(partition-by (comp boolean :a))
(partition 2 1)
(mapcat (fn [[lbounds rbounds]]
(map #(vector (last lbounds) %)
rbounds))))
这给你完全相同的结果。
应用于您的数据,它看起来几乎相同(另一个结果生成 fn)
user> (defn hi-or-lo [item]
(item :high (item :low)))
#'user/hi-or-lo
user>
(->> data
(partition-by (comp boolean :high))
(partition 2 1)
(mapcat (fn [[lbounds rbounds]]
(let [left-bound (last lbounds)
left-val (hi-or-lo left-bound)]
(map #(let [right-val (hi-or-lo %)
diff (Math/abs (- right-val left-val))]
{:extremes [left-bound %]
:price-range diff
:midpoint (+ (min right-val left-val)
(/ diff 2))})
rbounds))))
(clojure.pprint/pprint))
它打印以下内容:
({:extremes
[{:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}],
:price-range 3.6500000000017074E-4,
:midpoint 1.1212725}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}],
:price-range 6.399999999999739E-4,
:midpoint 1.12141}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.121925, :time "2016-08-03T00:00:00.000000Z"}],
:price-range 8.350000000001412E-4,
:midpoint 1.1215074999999999}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12215, :time "2016-08-02T23:00:00.000000Z"}],
:price-range 0.001060000000000061,
:midpoint 1.12162}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12273, :time "2016-08-02T21:15:00.000000Z"}],
:price-range 0.0016400000000000858,
:midpoint 1.12191}
{:extremes
[{:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}],
:price-range 0.0022900000000001253,
:midpoint 1.1222349999999999}
{:extremes
[{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.119215, :time "2016-08-02T12:30:00.000000Z"}],
:price-range 0.004164999999999974,
:midpoint 1.1212975}
{:extremes
[{:high 1.12338, :time "2016-08-02T18:15:00.000000Z"}
{:low 1.118755, :time "2016-08-02T12:00:00.000000Z"}],
:price-range 0.004625000000000101,
:midpoint 1.1210675}
...
作为关于 "complex data manipulation" 问题的答案,我建议您从 clojure 核心查看所有集合的操作函数,然后尝试将任何任务分解为这些函数的应用程序。需要超越它们的情况并不多见。