2017-05-08 1 views
0

Ich versuche, eine Funktion mit recur zu schreiben, die die Sequenz schneiden, sobald er eine Wiederholung trifft ([1 2 3 1 4] sollte [1 2 3] zurück), das ist meine Funktion:clojure - ?, Konj enthält und wiederholen

(defn cut-at-repetition [a-seq] 
    (loop[[head & tail] a-seq, coll '()] 
    (if (empty? head) 
     coll 
     (if (contains? coll head) 
     coll 
     (recur (rest tail) (conj coll head)))))) 

Das erste Problem ist mit der contains?, die eine Ausnahme auslöst, versuchte ich es durch some ersetzen, aber mit keinem Erfolg. Das zweite Problem ist in dem recur Teil, der auch eine Ausnahme werfen wird

+0

'enthält?' Überprüft, ob ein Schlüssel z. ein Set oder eine Karte. es kann auch auf Array/Vektoren verwendet werden, um zu überprüfen, ob ein _index_ vorhanden ist. Es wird nicht auf Seqs unterstützt. auch "Kopf" gibt es einen Wert von Ihrem 'a-seq' (der erste) - und' leer? 'ist für die Überprüfung, wenn eine Liste leer ist. – cfrick

Antwort

3

Sie haben gemacht mehrere Fehler:

  • Sie contains? auf einer Sequenz benutzt habe. Es funktioniert nur auf assoziativen Sammlungen. Verwenden Sie stattdessen some. Sie haben das erste Element der Sequenz (head) für empty? getestet. Testen Sie die gesamte Sequenz.
  • Verwenden Sie einen Vektor, um die Antwort zu akkumulieren. conj fügt Elemente zu der Vorderseite einer Liste hinzu und kehrt die Antwort um.

diese Korrektur, erhalten wir

(defn cut-at-repetition [a-seq] 
    (loop [[head & tail :as all] a-seq, coll []] 
    (if (empty? all) 
     coll 
     (if (some #(= head %) coll) 
     coll 
     (recur tail (conj coll head)))))) 

(cut-at-repetition [1 2 3 1 4]) 
=> [1 2 3] 

Die oben genannten Arbeiten, aber es ist langsam, da es die gesamte Sequenz für jedes fehlende Element abtastet. Also besser ein Set verwenden.

Nennen wir die Funktion take-distinct, da es take-while ähnlich ist. Wenn wir diesen Präzedenzfall und machen es faul folgen, können wir es tun so:

(defn take-distinct [coll] 
    (letfn [(td [seen unseen] 
       (lazy-seq 
       (when-let [[x & xs] (seq unseen)] 
        (when-not (contains? seen x) 
        (cons x (td (conj seen x) xs))))))] 
    (td #{} coll))) 

Wir die erwarteten Ergebnisse für die Finite-Sequenzen erhalten:

(map (juxt identity take-distinct) [[] (range 5) [2 3 2]] 
=> ([[] nil] [(0 1 2 3 4) (0 1 2 3 4)] [[2 3 2] (2 3)]) 

Und wir können so viel nehmen, wie wir von einem benötigen endlose Folge: ->mapv Präzedenzfall

(take 10 (take-distinct (range))) 
=> (0 1 2 3 4 5 6 7 8 9) 

würde ich Ihre eifrige Version take-distinctv, auf dem map nennen. Und ich würde es auf diese Weise tun:

(defn take-distinctv [coll] 
    (loop [seen-vec [], seen-set #{}, unseen coll] 
    (if-let [[x & xs] (seq unseen)] 
     (if (contains? seen-set x) 
     seen-vec 
     (recur (conj seen-vec x) (conj seen-set x) xs)) 
     seen-vec))) 

Beachten Sie, dass wir die gesehenen Elemente zweimal tragen:

  • als Vektor, als die Lösung zurückzukehren; und
  • als Set, um die Mitgliedschaft von zu testen.

Zwei der drei Fehler wurden von @cfrick kommentiert.

0

Es gibt einen Kompromiss zwischen dem Einsparen einer Linie oder zwei und das Bilden der Logik als einfach & ausdrücklich als möglich. Um es so offensichtlich wie möglich, würde ich es tun, so etwas wie diese:

(defn cut-at-repetition 
    [values] 
    (loop [remaining-values values 
     result   []] 
    (if (empty? remaining-values) 
     result 
     (let [found-values (into #{} result) 
      new-value (first remaining-values)] 
     (if (contains? found-values new-value) 
      result 
      (recur 
      (rest remaining-values) 
      (conj result new-value))))))) 

(cut-at-repetition [1 2 3 1 4]) => [1 2 3] 

Auch sollten Sie ein Lesezeichen The Clojure Cheatsheet und hält immer einen Browser-Tab, um es zu öffnen.

+1

Das Erstellen eines brandneuen Sets für jede Iteration ist sehr teuer und es wäre genauso einfach, es inkrementell zu erstellen. – amalloy

+0

Ja, aber da es für Instruktionszwecke gedacht ist, wollte ich vermeiden, sowohl 'result' als auch 'found-values' im 'loop-recur' zu haben. –

+0

... dann können Sie die Elemente genauso gut testen, wie Sie vorgehen, anstatt einen Satz zum Testen zu sammeln. – Thumbnail

0

Ich mag würde Feedback zu diesem Nutzenfunktion hören, die ich für mich selbst geschrieben (verwendet filter mit Stateful pred anstelle eines loop):

(defn my-distinct 
    "Returns distinct values from a seq, as defined by id-getter." 
    [id-getter coll] 
    (let [seen-ids (volatile! #{}) 
     seen? (fn [id] (if-not (contains? @seen-ids id) 
          (vswap! seen-ids conj id)))] 
    (filter (comp seen? id-getter) coll))) 

(my-distinct identity "abracadabra") 
; (\a \b \r \c \d) 

(->> (for [i (range 50)] {:id (mod (* i i) 21) :value i}) 
    (my-distinct :id) 
    pprint) 

; ({:id 0, :value 0} 
; {:id 1, :value 1} 
; {:id 4, :value 2} 
; {:id 9, :value 3} 
; {:id 16, :value 4} 
; {:id 15, :value 6} 
; {:id 7, :value 7} 
; {:id 18, :value 9}) 

Docs von filter sagt „pred frei von Neben sein müssen -Effekte ", aber ich bin mir nicht sicher, ob es in diesem Fall in Ordnung ist. Ist filter garantiert, um über die Reihenfolge in der Reihenfolge zu iterieren und nicht zum Beispiel Sprünge vorwärts zu nehmen?

+0

Ich denke, 'filter' will' pred' frei von Nebenwirkungen sein, um zu verhindern, dass Chunking zu Überraschungen führt. – Thumbnail

+0

dachte ich auch. Wenn "Filter" Multithreading verwendet, ist diese Konstruktion von Natur aus gefährlich. :/In diesem Fall wäre "Atom" geeigneter. – NikoNyrh