Frage

Ich lerne Clojure und würde ein paar Ratschläge auf idiomatische Verwendung mag. Im Rahmen eines kleinen Statistik-Pakets, habe ich eine Funktion, um den Modus einen Satz von Daten zu berechnen. (Hintergrund:... Der Modus ist der häufigste Wert in einem Satz von Daten Es gibt fast ein Dutzend veröffentlichte Algorithmen zu dessen Berechnung Der eine hier verwendete aus „Grundlagen der Biostatistik“ 6. Auflage von Bernard Rosner)

(defn tally-map
 " Create a map where the keys are all of the unique elements in the input
   sequence and the values represent the number of times those elements
   occur. Note that the keys may not be formatted as conventional Clojure
   keys, i.e. a colon preceding a symbol."
  [aseq]
  (apply merge-with + (map (fn [x] {x 1}) aseq)))

(defn mode
 " Calculate the mode. Rosner p. 13. The mode is problematic in that it may
   not be unique and may not exist at all for a particular group of data.
   If there is a single unique mode, it is returned. If there are multiple
   modes, they are returned as a list. If there is no mode, that is all
   elements are present in equal frequency, nil is returned."
  [aseq]
  (let [amap (tally-map aseq)
        mx (apply max (vals amap))
        k (keys amap)
        f (fn [x] (not (nil? x)))
        modes (filter f (map #(if (= mx (get amap %)) %) k))
        ]
    (cond (= 1 (count modes)) (first modes)
      (every? #(= mx %) (vals amap)) nil
      :else modes)
    )
  )

Es gibt ein paar Dinge, die ich habe Fragen zu:

  1. Das Argument. Die Funktion übernimmt eine einzelne Sequenz. Ist es mehr idiomatische eine variable Anzahl von Argumenten wie die Additionsfunktion zu übernehmen?
  2. Code-Geruch. Es scheint, wie die „let“ ist ein bisschen komplizierter, als es sein sollte - so viele variable Zuweisungen. jede offensichtliche (oder nicht so offensichtlich) habe ich verpasst nutzt der Sprache oder Bibliothek, die diese Methode prägnanter machen würde?

Vielen Dank im Voraus für die Hilfe.

War es hilfreich?

Lösung

Hier ist mein nehmen:

  1. Es gibt viele Kerne clojure Funktionen sind, die Sequenzen als Argumente, während andere mehrere Argumente nehmen, so gibt es keinen wirklichen idiomatischen Weg meiner Meinung nach. Wenn Sie bereits Ihre Daten in einer Folge haben, würde ich eine seq als Argument verwenden, da es Ihnen einen Anruf sparen anzuwenden.

  2. Ich würde eine Funktion nicht schreiben, die einen Wert in einigen Fällen und eine Liste von Werten in anderen zurück, weil der Code Berufung immer den Rückgabewert überprüfen müssen, bevor es zu benutzen. Stattdessen würde ich mit nur einem Punkt in eine Single-Mode als Seq zurückzukehren. Aber Sie können Ihre Gründe haben, je nach dem Code, der diese Funktion aufruft.

von Apart, dass ich den Modus Funktion wie folgt neu schreiben würde:

(defn mode [aseq]
  (let [amap (tally-map aseq)
        mx (apply max (vals amap))
        modes (map key (filter #(= mx (val %)) amap))
        c (count modes)]
    (cond
      (= c 1) (first modes)
      (= c (count amap)) nil
      :default modes)))

Statt der Definition einer Funktion f Sie die Identitätsfunktion verwenden können (es sei denn, Ihre Daten enthält Werte, die logisch falsch sind). Aber Sie haben nicht einmal das brauchen. Ich finde die Modi in einer anderen Art und Weise, die besser lesbar ist für mich: Die Karte amap wirkt als eine Folge von Map-Einträgen (Schlüssel-Wert-Paare). Erste filtere ich nur die Einträge, die den Wert mx haben. Dann Karte ich die Tastenfunktion auf diese, mir eine Folge von Tasten zu geben.

Um zu überprüfen, ob es irgendwelche Modi ich wieder über die Karte nicht in einer Schleife zu tun. Stattdessen vergleiche ich nur die Anzahl der Modi auf die Anzahl der Map-Einträge. Wenn sie gleich sind, haben alle Elemente die gleiche Frequenz!

Hier ist die Funktion, die immer eine seq zurückgibt:

(defn modes [aseq]
  (let [amap (tally-map aseq)
        mx (apply max (vals amap))
        modes (map key (filter #(= mx (val %)) amap))]
    (when (< (count modes) (count amap)) modes)))

Andere Tipps

Meiner Meinung nach ist die Abbildung eine Funktion über eine Sammlung und dann sofort die Liste Kondensation bis zu einem Punkt, ist ein Zeichen reduce zu verwenden.

(defn tally-map [coll]
  (reduce (fn [h n]
            (assoc h n (inc (h n 0))))
          {} coll))

In diesem Fall würde ich die mode fn schreibt eine einzige Sammlung als Argument zu nehmen, wie Sie. Der einzige Grund, warum ich denken kann mehrere Argumente für eine Funktion wie diese zu verwenden, wenn Sie vorhaben, viel wörtliche Argumente eingeben zu müssen.

Also, wenn z.B. Dies ist für eine interaktive REPL Skript und Sie gehen oft (mode [1 2 1 2 3]) wörtlich zu tippen, dann sollten Sie die Funktion übernehmen mehrere Argumente haben, die Sie von der Eingabe des zusätzlichen [] in der Funktion aufrufen, die ganze Zeit zu sparen. Wenn Sie planen, viele Zahlen aus einer Datei zu lesen und dann den Modus dieser Zahlen nehmen, hat dann die Funktion ein einziges Argument, das eine Sammlung ist, so dass Sie sich von der Verwendung apply die ganze Zeit sparen. Ich bin Ihre häufigste Anwendungsfall raten letztere ist. Ich glaube apply auch Overhead hinzufügt, dass Sie vermeiden, wenn Sie einen Funktionsaufruf, die eine Sammlung Argument.

ich mit anderen einverstanden, dass Sie sollten eine Liste der Ergebnisse angezeigt haben mode auch wenn es nur ist, ein; es wird Ihr Leben leichter machen. Vielleicht benennen Sie es modes, während Sie gerade dabei sind.

Hier ist eine nette kurze Implementierung von mode:

(defn mode [data] 
  (first (last (sort-by second (frequencies data)))))

Dies nutzt die folgenden Fakten:

  • Die frequencies Funktion gibt eine Karte von Werten -> Frequenzen
  • Sie können eine Karte als eine Folge von Schlüssel-Wert-Paare behandeln
  • Wenn Sie diese Sequenz nach Wert sortiert werden (das second Element in jedem Paar), dann das letzte Element in der Sequenz stellt den Modus

Bearbeiten

Wenn Sie die Multimodus-Fall behandeln, dann können Sie ein zusätzliches partition-by einfügen alle mit der maximalen Frequenz, die Werte zu halten:

(defn modes [data] 
  (->> data
       frequencies 
       (sort-by second)
       (partition-by second)
       last
       (map first)))

Sieht für mich in Ordnung. Ich würde ersetzen die

f (fn [x] (not (nil? x)))
mode (filter f (map #(if (= mx (get amap %)) %) k))

mit

mode (remove nil? (map #(if (= mx (get amap %)) %) k))

(ich weiß nicht, warum so etwas wie not-nil? nicht in clojure.core ist;., Es ist etwas, das man jeden Tag braucht)

  

Wenn es ein einziger einzigartiger Modus ist, wird zurückgegeben. Wenn es mehrere Modi sind, werden sie als Liste zurück. Wenn es keinen Modus, dh alle Elemente in gleicher Häufigkeit vorhanden sind, gleich Null zurückgeführt wird. "

Man könnte darüber nachdenken, einfach ein seq Rückkehr jedes Mal (ein Element oder leer ist in Ordnung); Andernfalls haben die Fälle durch den anrufenden Code zu unterscheiden. Durch immer ein seq Rückkehr, wird Ihr Ergebnis magisch arbeitet als Argument für andere Funktionen, die ein f erwarten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top