Комплекс обработки данных в 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-low) и среднюю точку (average of high & low), но я не хочу, чтобы создавались все возможные пары.
То, что я хочу сделать, это начать с 1-го пункта в коллекции {:high 1.121455, :time "2016-08-03T05:15:00.000000Z"}
и пройти "вниз" через оставшуюся часть коллекции. коллекция, создавая пару с каждым элементом :low
, пока я не нажму на следующий элемент :high
. Как только я попаду в следующий пункт :high
, меня больше не интересуют никакие другие пары. В этом случае создается только одна пара, которая является :high
и 1 - й :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"}]}
Затем я перейду ко 2-му элементу в коллекции {:low 1.12109, :time "2016-08-03T05:15:00.000000Z"}
и пройдусь "вниз" по остальной части коллекции, создавая пару с каждый пункт :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
, поэтому я останавливаюсь на этом.
{:high 1.12173, :time "2016-08-03T04:30:00.000000Z"}
и шел "вниз", создавая пары с каждым :low
, пока не попадал в следующий :high
. В в этом случае я получаю 0 пар, генерируемых, потому что за :high
немедленно следует другой :high
. То же самое для следующих 3: высоких пунктов, за которыми сразу же следует другой :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, я бы, вероятно, использовал пару вложенных циклов, используйте a break
, чтобы выйти из внутреннего цикла, когда я перестал видеть :high
s для сопряжения с моим :low
и наоборот, и накапливать все сгенерированные записи в массив по мере прохождения 2 циклов. Я просто не могу придумать хороший способ атаковать его с помощью Clojure...
Есть идеи?
1 ответ:
Прежде всего, вы можете перефразировать это следующим образом:
- вы должны найти все граничные точки, где
:high
следует за:low
, или наоборот- Вам нужно взять элемент до привязки и сделать что-то с ним и каждым элементом после привязки, но до следующего переключения привязки.
Для простоты воспользуемся следующей моделью данных:
(def data0 [{:a 1} {:b 2} {:b 3} {:b 4} {:a 5} {:a 6} {:a 7}])
Первая часть может быть достигнута с помощью функции
partition-by
, которая разбивает сбор входных данных каждый раз, когда функция изменяет свое значение для обрабатываемого элемента:Теперь вам нужно взять каждую из этих двух групп и манипулировать ими. группы должны быть такими: [({: a 1}) ({: b 2} {:b 3} {: b 4})] [({: b 2} {:b 3} {:b 4}) ({: a 5} {:A 6} {: a 7})]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}))
Это достигается функцией
partition
:Вы должны что-то сделать для каждой пары групп. Вы можете сделать это с помощью map: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})))
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}])
Вот результат, которого мы хотим (это почти так, поскольку мы просто генерируем векторы, а не карты).
Теперь вы можете украсить его с помощью макроса threading:
(->> 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))
Он печатает следующее:
В качестве ответа на вопрос о "сложных манипуляциях с данными" я бы посоветовал вам просмотреть все манипулирующие функции коллекций из ядра clojure, а затем попытаться разложить любую задачу на их применение. Не так уж много случаев, когда вам нужно что-то сверх них.({: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} ...