Clojure中的惯用模式功能

cla*_*taq 8 statistics clojure

我正在学习Clojure,并希望得到一些关于惯用法的建议.作为小型统计数据包的一部分,我有一个计算一组数据模式的函数.(背景:模式是一组数据中最常见的值.有几十种已发布的算法可供计算.这里使用的算法来自伯纳德罗斯纳的"生物统计学基础"第6版.)

(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)
    )
  )
Run Code Online (Sandbox Code Playgroud)

有几件事我有疑问:

  1. 争论.该函数接受单个序列.接受像加法函数这样的可变数量的参数是不是更惯用?
  2. 代码味道.似乎"let"比它应该更复杂 - 如此多的变量赋值.我是否错过了使该方法更简洁的语言或库的任何明显(或不那么明显)用法?

在此先感谢您的帮助.

Bri*_*per 5

在我看来,将一些函数映射到一个集合上,然后立即将列表缩减为一个项目是一个可以使用的标志reduce.

(defn tally-map [coll]
  (reduce (fn [h n]
            (assoc h n (inc (h n 0))))
          {} coll))
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我会写modefn以单个集合作为参数,就像你所做的那样.我可以想到为这样的函数使用多个参数的唯一原因是你计划必须经常输入文字参数.

因此,如果这是一个交互式REPL脚本并且您经常要按(mode [1 2 1 2 3])字面输入,那么您应该让该函数采用多个参数,以免您[]在函数调用中始终输入额外内容.如果您打算从文件中读取大量数字然后采用这些数字的模式,那么让该函数采用一个集合的参数,这样您就可以免于一直使用apply.我猜你最常见的用例是后者.我相信apply还会增加您在进行带有集合参数的函数调用时避免的开销.

我同意其他人的意见,mode即使只有一个结果,你应该返回结果清单; 它会让你的生活更轻松.也许modes你在它的时候重命名它.


mik*_*era 5

这是一个很好的简洁实现mode

(defn mode [data] 
  (first (last (sort-by second (frequencies data)))))
Run Code Online (Sandbox Code Playgroud)

这利用了以下事实:

  • frequencies函数返回值 -> 频率的映射
  • 您可以将映射视为一系列键值对
  • 如果按值(second每对中的项目)对此序列进行排序,则序列中的最后一项将代表众数

编辑

如果您想处理多模式情况,那么您可以插入一个额外的内容partition-by来保留所有具有最大频率的值:

(defn modes [data] 
  (->> data
       frequencies 
       (sort-by second)
       (partition-by second)
       last
       (map first)))
Run Code Online (Sandbox Code Playgroud)


Chr*_*erg 4

这是我的看法:

  1. 有许多核心 clojure 函数将序列作为参数,而其他函数则采用多个参数,因此我认为没有真正的惯用方法。如果您已经将数据放在序列中,我会使用 seq 作为参数,因为它会节省您对 apply 的调用。

  2. 我不会编写一个在某些情况下返回值而在其他情况下返回值列表的函数,因为调用代码在使用返回值之前始终必须检查它。相反,我会返回一个单一模式作为一个序列,其中只有一个项目。但您可能有自己的理由,具体取决于调用此函数的代码。

除此之外,我会像这样重写模式函数:

(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)))
Run Code Online (Sandbox Code Playgroud)

您可以使用恒等函数而不是定义函数 f (除非您的数据包含逻辑上错误的值)。但你甚至不需要那个。我以不同的方式找到模式,这对我来说更具可读性:映射 amap 充当映射条目(键值对)的序列。首先,我仅过滤那些具有值 mx 的条目。然后我将按键功能映射到这些按键上,给出一系列按键。

为了检查是否有任何模式,我不再循环遍历地图。相反,我只是将模式数量与地图条目数量进行比较。如果它们相等,则所有元素都具有相同的频率!

这是始终返回 seq 的函数:

(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)))
Run Code Online (Sandbox Code Playgroud)