2016-08-03 26 views
3

Ich arbeite an einem persönlichen Marktanalyseprojekt. Ich habe eine Datenstruktur hat alle bisherigen Wendepunkte auf dem Markt vertreten, die wie folgt aussieht:Komplexe Datenmanipulation 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"}] 

Konzeptionell Ich möchte der Preisklasse zusammenpassen :high/:low Paare, trainieren (High-Low) und Mittelpunkt (Durchschnitt von hoch & niedrig), aber ich möchte nicht jedes mögliche Paar generiert werden.

Was ich tun möchte, ist vom ersten Elemente in der Auflistung beginnen {:high 1.121455, :time "2016-08-03T05:15:00.000000Z"} und zu Fuß „nach unten“ durch den Rest der Sammlung, ein Paar mit jedem :low Elemente zu schaffen, bis ich den nächsten :high Punkt getroffen. Sobald ich den nächsten :high Artikel getroffen habe, bin ich nicht an weiteren Paaren interessiert. In diesem Fall wird nur ein einziges Paar erstellt, nämlich das :high und das 1. :low - ich höre dort auf, weil das nächste (dritte) Element ein :high ist. Die 1 Rekord wie {: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"}]}

Next aussehen soll ich auf das zweite Element in der Sammlung bewegen würde {:low 1.12109, :time "2016-08-03T05:15:00.000000Z"} und zu Fuß „nach unten“ durch den Rest der Sammlung, ein Paar mit jedem :high Elemente zu schaffen UNTIL traf ich die nächste :low Artikel. In diesem Fall erhalte ich 5 neue Datensätze, die :low und die nächsten 5 :high Elemente sind, die alle aufeinander folgen; die erste dieser 5 sehen die Einträge wie die zweite dieser 5 Aufzeichnungen wie

{: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"}]} 

und so weiter aussehen würde

{: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"}]} 

würde.

Danach bekomme ich eine :low, also höre ich dort auf.

Dann würde ich auf den 3. Artikel {:high 1.12173, :time "2016-08-03T04:30:00.000000Z"} bewegen und gehen Sie "nach unten" erstellen Paare mit jeder :low BIS ich die nächste :high traf. In diesem Fall bekomme ich 0 Paare generiert, weil auf die :high sofort eine weitere :high folgt. Das Gleiche gilt für die nächsten 3: hohe Gegenstände, die alle sofort von einer anderen :high

Weiter gefolgt sind komme ich zum 7. Punkt {:high 1.12338, :time "2016-08-02T18:15:00.000000Z"} und das soll ein Paar mit jedem der folgenden 20 :low Elementen erzeugen.

Mein erzeugte Ergebnis wäre eine Liste aller Paare erstellt:

[{: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"}]} 
... 

Wenn ich diese mit so etwas wie Python wurde die Umsetzung, ich würde wahrscheinlich ein paar verschachtelten Schleifen verwenden, ein break verwenden, um zu beenden die innere Schleife, als ich aufhörte, :high s zu sehen, um mit meiner :low und umgekehrt zu koppeln, und sammelte alle erzeugten Aufzeichnungen in ein Array, während ich die 2 Schleifen durchquerte. Ich kann einfach nicht einen guten Weg, um es mit Clojure angreifen ...

Irgendwelche Ideen?

+0

Allgemeine Hinweise: Brechen Sie das Problem in kleine Funktionen und sehen Sie sich "reduce"/"reduced" + Beispiele ihrer Verwendung an. –

+1

Dies ist eine weitere Formation der allgemeinen Interviewfrage "Wie viel Wasser kann ein Histogramm halten?" Ich denke, ich werde dieses stattdessen verwenden, um die Leute dazu zu bringen, abstrakter darüber zu denken –

+0

Gehen Sie voran und verwenden Sie Ihre verschachtelten Schleifen und Pausen.In Clojure würden Sie normalerweise 'loop' und' recur' stattdessen verwenden – skrat

Antwort

5

allererst können Sie diese auf folgende Weise neu formulieren:

  1. Sie haben alle Grenzpunkte zu finden, in :high von :low gefolgt wird oder umgekehrt
  2. benötigen Sie den Artikel nehmen vor die Grenze, und mach etwas damit und jeden Gegenstand nach gebunden, aber bis zum nächsten Wechsel gebunden.

für die Einfachheit lassen Sie uns das folgende Datenmodell verwenden:

(def data0 [{:a 1} {:b 2} {:b 3} {:b 4} {:a 5} {:a 6} {:a 7}]) 

der erste Teil durch die Verwendung partition-by Funktion erreicht werden kann, dass die Eingangs Sammlung die Funktion jedes Mal, teilt sie den Wert für das verarbeitete Element ändert :

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

Jetzt müssen Sie alle zwei dieser Gruppen nehmen und sie manipulieren. Die Gruppen sollten folgendermaßen aussehen: [({: a 1}) ({: b 2} {: b 3} {: b 4})] [({: b 2} {: b 3} {: b 4}) ({: 5} {: a 6} {: 7})]

dies durch die partition Funktion erreicht:

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

Sie etwas für jedes Paar von Gruppen zu tun . Man könnte es mit der Karte tun:

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

aber da müssen Sie die verketteten Liste, eher dann die gruppiert man, würden Sie wollen, mapcat verwenden, anstatt 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}]) 

, dass das Ergebnis, das wir ist wollen (Es ist fast so, da wir einfach Vektoren anstelle von Karten generieren).

jetzt können Sie es mit dem Einfädeln Makro prettify:

(->> data0 
    (partition-by (comp boolean :a)) 
    (partition 2 1) 
    (mapcat (fn [[lbounds rbounds]] 
       (map #(vector (last lbounds) %) 
        rbounds)))) 

, die Ihnen genau das gleiche Ergebnis gibt.

auf Ihre Daten angewendet es das gleiche (mit einem anderen Ergebnis zu erzeugen 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)) 

sehen fast wäre es druckt die folgenden Möglichkeiten:

({: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} 
... 

Als Antwort die Frage nach dem „komplexen Datenmanipulation "Ich würde dir raten, alle Manipulationsfunktionen der Sammlungen aus dem Clojure-Kern zu betrachten und dann zu versuchen, jede Aufgabe auf ihre Anwendung zu zerlegen. Es gibt nicht so viele Fälle, in denen Sie etwas über sie hinaus brauchen.

+0

Fantastische Antwort - danke für die Lektion! Ich kann Ihrem Denken folgen, wie Sie das Problem in eine Reihe von kleineren Problemen zerlegt haben, und ich bin mir ziemlich sicher, dass ich mindestens zwei Stufen in meinem Verständnis der Clojure-Datenmanipulation erreicht habe, jetzt verstehe ich die Lösung. Jetzt mache ich das, was Sie vorgeschlagen haben - arbeiten Sie (wieder) durch die Sammlung von Sammlungsmanipulationsfunktionen in clojure.core und versuchen Sie zu absorbieren, wie sie zusammenhängen, wie Sie es so klar umrissen haben – monch1962