2009-10-21 8 views
8

J'apprends Clojure et je voudrais des conseils sur l'utilisation idiomatique. Dans le cadre d'un petit paquet de statistiques, j'ai une fonction pour calculer le mode d'un ensemble de données. (Contexte: Le mode est la valeur la plus commune dans un ensemble de données.Il y a près d'une douzaine d'algorithmes publiés pour le calculer.Cela utilisé ici provient de "Fundamentals of Biostatistics" 6th Ed par Bernard Rosner.)Fonction du mode idiomatique dans Clojure

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

Il y a deux ou trois choses que j'ai des questions sur:

  1. L'argument. La fonction accepte une seule séquence. Est-il plus idiomatique d'accepter un nombre variable d'arguments comme la fonction d'addition?
  2. Odeur du code. Il semble que le "let" soit un peu plus compliqué qu'il ne devrait l'être - autant d'assignations de variables. Ai-je manqué des utilisations évidentes (ou pas si évidentes) du langage ou de la bibliothèque qui rendraient cette méthode plus concise?

Merci d'avance pour l'aide.

Répondre

4

Voici mon:.

  1. Il y a beaucoup de coeur fonctions clojure qui prennent des séquences comme arguments tandis que d'autres prennent plusieurs arguments, donc Il n'y a pas vraiment de manière idiomatique à mon avis. Si vous avez déjà vos données dans une séquence, j'utiliserais un seq comme argument, puisqu'il vous permettra d'économiser un appel à appliquer.

  2. Je n'écrirais pas une fonction qui renvoie une valeur dans certains cas et une liste de valeurs dans d'autres, car le code appelant devra toujours vérifier la valeur de retour avant de l'utiliser. Au lieu de cela, je retournerais un seul mode en tant que seq avec juste un élément. Mais vous pouvez avoir vos raisons, selon le code qui appelle cette fonction.

En dehors de cela, je réécrire la fonction de mode comme celui-ci:

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

Au lieu de définir une fonction f, vous pouvez utiliser la fonction d'identité (à moins que vos données contiennent des valeurs qui sont logiquement faux). Mais tu n'en as même pas besoin.Je trouve les modes d'une manière différente, ce qui est plus lisible pour moi: L'amap de carte agit comme une séquence d'entrées de carte (paires clé-valeur). D'abord je filtre seulement les entrées qui ont la valeur mx. Ensuite, je cartographie la fonction clé sur ceux-ci, en me donnant une séquence de clés.

Pour vérifier s'il existe des modes, je ne recommence pas la boucle sur la carte. Au lieu de cela, je viens de comparer le nombre de modes au nombre d'entrées de la carte. Si elles sont égales, tous les éléments ont la même fréquence!

est ici la fonction qui renvoie toujours suivants:

(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))) 
+0

"La fonction que vous définissez est vraiment la fonction d'identité (puisque nil est logiquement faux)." Non, loin de là.Comparez les résultats de (identité de carte [true false nul 1]) et (carte # (non (néant?% 1)) [true false nul 1]). – pmf

+0

Vous avez bien sûr raison, ce n'est pas la même fonction. Je voulais dire qu'il pourrait utiliser la fonction d'identité à sa place dans cet exemple. Je vais corriger cela. –

+0

Merci pour l'analyse et la suggestion. C'est juste le changement de perspective que je cherchais. – clartaq

2

Cela me semble bien. Je remplacerai la

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

avec

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

(Je ne sais pas pourquoi quelque chose comme not-nil? est pas clojure.core, c'est quelque chose qu'on a besoin tous les jours.)

S'il existe un seul mode unique, il est renvoyé. S'il existe plusieurs modes, ils sont renvoyés sous forme de liste. S'il n'y a pas de mode, qui est tous les éléments sont présents en fréquence égale, nil est retournée «

Vous pourriez penser simplement retourner un seq chaque fois (un élément ou vide est très bien);. Sinon, les cas . doivent être différenciés par le code d'appel en retournant toujours un seq, votre résultat fonctionnera comme par magie comme argument à d'autres fonctions qui attendent une seq

+0

Merci pour la suggestion. La façon dont les valeurs de retour ont été établies était un non-sens. C'était un espoir futile et de courte durée d'utiliser la fonction de la même façon que j'ai utilisé la moyenne et la médiane, qui retournent une seule valeur. – clartaq

5

À mon avis, une fonction cartographie sur une collection, puis condenser immédiatement la liste à un élément est un signe d'utiliser reduce.

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

Dans ce cas, j'écrirait le mode fn de prendre une seule collection comme argument, comme vous l'avez fait. La seule raison pour laquelle je peux penser à utiliser plusieurs arguments pour une fonction comme celle-ci est si vous prévoyez d'avoir à taper beaucoup d'arguments littéraux.

Donc si par ex. C'est pour un script REPL interactif et vous allez souvent taper (mode [1 2 1 2 3]) littéralement, alors vous devriez avoir la fonction prendre plusieurs arguments, pour vous éviter de taper le [] supplémentaire dans l'appel de fonction tout le temps. Si vous envisagez de lire un grand nombre de nombres à partir d'un fichier, puis prenez le mode de ces nombres, alors la fonction prend un seul argument qui est une collection de sorte que vous pouvez vous éviter d'utiliser apply tout le temps. Je suppose que votre cas d'utilisation le plus courant est celui-ci. Je crois que apply ajoute également une surcharge que vous évitez lorsque vous avez un appel de fonction qui prend un argument de collection.

Je suis d'accord avec les autres que vous devriez avoir mode retourner une liste de résultats, même s'il y en a seulement un; ça va te rendre la vie plus facile. Peut-être renommer modes pendant que vous y êtes.

+0

J'ai suivi votre conseil et renommé ma seconde fonction en modes. :-) –

+0

(inc (ou (h n) 0)) est le même que (inc (h n 0)) :) –

+0

Oh, à droite, je l'oublie toujours cette option de valeur par défaut. Merci. –

4

est ici une belle mise en œuvre concise de mode:

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

Ce exploite les faits suivants:

  • La fonction frequencies retourne une carte des valeurs -> fréquences
  • Vous pouvez traiter une carte en tant que séquence de paires clé-valeur
  • Si vous triez cette séquence par valeur (l'élément second dans chaque paire), le dernier élément de la séquence représentera le mode

EDIT

Si vous voulez gérer le cas multimodes alors vous pouvez insérer une partition-by supplémentaire pour garder toutes les valeurs avec la fréquence maximale:

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

ce que le cas où le mode est pas unique, ou n'existe pas? – georgek

+1

Merci d'avoir répondu à cette vieille question (retour dans les 1.0 pré jours je crois.) La fonction des fréquences n'existait pas à l'époque. C'est sympa de recevoir une note pour revoir les anciennes fonctions pour pouvoir les mettre à jour. – clartaq

Questions connexes