2015-12-01 25 views
6

Sto cercando di fare un trasduttore condizionale in Clojure come segue:Implementazione Clojure condizionale/branching trasduttore

(defn if-xf 
    "Takes a predicate and two transducers. 
    Returns a new transducer that routes the input to one of the transducers 
    depending on the result of the predicate." 
    [pred a b] 
    (fn [rf] 
    (let [arf (a rf) 
      brf (b rf)] 
     (fn 
     ([] (rf)) 
     ([result] 
      (rf result)) 
     ([result input] 
      (if (pred input) 
      (arf result input) 
      (brf result input))))))) 

E 'molto utile in quanto permette di fare cose come questa:

;; multiply odd numbers by 100, square the evens. 
(= [0 100 4 300 16 500 36 700 64 900] 
    (sequence 
      (if-xf odd? (map #(* % 100)) (map (fn [x] (* x x)))) 
      (range 10))) 

Tuttavia, questo trasduttore condizionale non funziona molto bene con i trasduttori che eseguono la pulizia nel loro ramo 1-arity:

;; negs are multiplied by 100, non-negs are partitioned by 2 
;; BUT! where did 6 go? 
;; expected: [-600 -500 -400 -300 -200 -100 [0 1] [2 3] [4 5] [6]] 
;; 
(= [-600 -500 -400 -300 -200 -100 [0 1] [2 3] [4 5]] 
(sequence 
    (if-xf neg? (map #(* % 100)) (partition-all 2)) 
    (range -6 7))) 

È possibile modificare la definizione di if-xf per gestire il caso dei trasduttori con la pulizia?

sto cercando questo, ma con un comportamento strano:

(defn if-xf 
    "Takes a predicate and two transducers. 
    Returns a new transducer that routes the input to one of the transducers 
    depending on the result of the predicate." 
    [pred a b] 
    (fn [rf] 
    (let [arf (a rf) 
      brf (b rf)] 
     (fn 
     ([] (rf)) 
     ([result] 
      (arf result) ;; new! 
      (brf result) ;; new! 
      (rf result)) 
     ([result input] 
      (if (pred input) 
      (arf result input) 
      (brf result input))))))) 

In particolare, il lavaggio avviene alla fine:

;; the [0] at the end should appear just before the 100. 
(= [[-6 -5] [-4 -3] [-2 -1] 100 200 300 400 500 600 [0]] 
     (sequence 
     (if-xf pos? (map #(* % 100)) (partition-all 2)) 
     (range -6 7))) 

C'è un modo per rendere questa ramificazione/trasduttore condizionale senza memorizzando l'intera sequenza di input nello stato locale all'interno di questo trasduttore (cioè eseguendo tutta l'elaborazione nel ramo 1-arity al momento della pulizia)?

risposta

1

L'idea è di completare ogni volta che il trasduttore si commuta. IMO questo è l'unico modo per farlo senza buffering:

(defn if-xf 
    "Takes a predicate and two transducers. 
    Returns a new transducer that routes the input to one of the transducers 
    depending on the result of the predicate." 
    [pred a b] 
    (fn [rf] 
    (let [arf (volatile! (a rf)) 
      brf (volatile! (b rf)) 
      a? (volatile! nil)] 
     (fn 
     ([] (rf)) 
     ([result] 
     (let [crf (if @a? @arf @brf)] 
      (-> result crf rf))) 
     ([result input] 
     (let [p? (pred input) 
       [xrf crf] (if p? [@arf @brf] [@brf @arf]) 
       switched? (some-> @a? (not= p?))] 
      (if switched? 
      (-> result crf (xrf input)) 
      (xrf result input)) 
      (vreset! a? p?))))))) 
(sequence (if-xf pos? (map #(* % 100)) (partition-all 2)) [0 1 0 1 0 0 0 1]) 
; => ([0] 100 [0] 100 [0 0] [0] 100) 
+0

'(sequenza (se-xf pos? (Mappa # (*% 100)) (partizione-tutto 2)) [-1 1 0])' dà '([-1] 100)' nell'esempio, questo potrebbe non essere ciò che l'OP vuole – Davyzhu

+0

Hai ragione. Devo rimuovere il 'vresets' e sta funzionando. Ma ora non capisco più perché non funzioni. :( – ClojureMostly

+0

Perché 'arf' e' brf' devono essere volatili - non sembrano cambiare? Penso che con la restrizione che non possiamo bufferizzare, questo è probabilmente il migliore possibile. Sfortunatamente questa soluzione non lo fa t abbastanza lavoro in casi patologici, (ad esempio se uno dei trasduttori argomento buffer tutto e fa tutto il lavoro nel passaggio di completamento 1-arg). Sarebbe super cool se ci fosse un degrado aggraziato, dove se nessuno dei buffer di input xducer, quindi il 'if' non bufferizzerebbe, e altrimenti buffererà nella misura necessaria.I trasduttori non sono ancora magici, comunque. – dsg

1

Penso che la tua domanda sia mal definita. Che cosa esattamente vuoi che succeda quando i trasduttori hanno lo stato? Ad esempio, cosa vi aspettate questo do:

(sequence 
    (if-xf even? (partition-all 3) (partition-all 2)) 
    (range 14)) 

Inoltre, le funzioni a volte riducendo del lavoro da fare all'inizio e alla fine e non può essere riavviato in modo arbitrario. Ad esempio, ecco un riduttore che calcola la media:

(defn mean 
    ([] {:count 0, :sum 0}) 
    ([result] (double (/ (:sum result) (:count result)))) 
    ([result x] 
    (update-in 
    (update-in result [:count] inc) 
    [:sum] (partial + x)))) 
(transduce identity mean [10 20 40 40]) ;27.5 

Ora prendiamo la media, in cui nulla di sotto di 20 conta per il 20, ma tutto il resto è diminuito di 1:

(transduce 
    (if-xf 
    (fn [x] (< x 20)) 
    (map (constantly 20)) 
    (map dec)) 
    mean [10 20 40 40]) ;29.25 

La mia risposta è il seguente: Penso che la tua soluzione originale sia la migliore. Funziona bene con map, che è il modo in cui hai dichiarato l'utilità del trasduttore condizionale in primo luogo.