Statistics Clojure中的惯用模式函数

Statistics Clojure中的惯用模式函数,statistics,clojure,Statistics,Clojure,我正在学习Clojure,想听听关于惯用用法的建议。作为一个小型统计软件包的一部分,我有一个函数来计算一组数据的模式。(背景:模式是一组数据中最常见的值。计算模式的算法几乎有十几种。这里使用的是伯纳德·罗斯纳(Bernard Rosner)出版的第六版《生物统计学基础》中的一种。) 有几件事我有疑问: 争论。该函数接受单个序列。接受可变数量的参数(如加法函数)是否更为惯用 代码气味。看起来“let”比它应该的要复杂一点——这么多变量赋值。我是否遗漏了语言或库的任何明显(或不太明显)的用法,这些用

我正在学习Clojure,想听听关于惯用用法的建议。作为一个小型统计软件包的一部分,我有一个函数来计算一组数据的模式。(背景:模式是一组数据中最常见的值。计算模式的算法几乎有十几种。这里使用的是伯纳德·罗斯纳(Bernard Rosner)出版的第六版《生物统计学基础》中的一种。)

有几件事我有疑问:

  • 争论。该函数接受单个序列。接受可变数量的参数(如加法函数)是否更为惯用
  • 代码气味。看起来“let”比它应该的要复杂一点——这么多变量赋值。我是否遗漏了语言或库的任何明显(或不太明显)的用法,这些用法会使此方法更加简洁

  • 提前谢谢你的帮助。

    我觉得很好。我会换新的

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

    (我不知道为什么像
    not nil?
    这样的东西不在
    clojure.core
    中;它是人们每天都需要的东西。)

    如果存在单个唯一模式,则返回该模式。如果有多个模式,它们将作为列表返回。如果没有模式,即所有元素以相同频率出现,则返回nil。”

    您可以考虑每次只返回一个seq(一个元素或空就可以了);否则,必须通过调用代码来区分情况。通过始终返回seq,您的结果将神奇地作为预期seq的其他函数的参数。

    以下是我的观点:

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

  • 我不会编写在某些情况下返回值而在其他情况下返回值列表的函数,因为调用代码在使用它之前必须始终检查返回值。相反,我会返回一个单一模式作为seq,其中只有一项。但是,根据调用此函数的代码,您可能有自己的原因

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

    (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)))
    
    您可以使用identity函数(除非您的数据包含逻辑上为false的值),而不是定义函数f。但您甚至不需要它。我发现模式有不同的方式,这对我来说更容易理解:map amap充当一系列map条目(键值对).首先,我只过滤那些值为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)))
    
    (defn模式[aseq]
    (让[amap(理货图aseq)
    mx(应用最大值(VAL amap))
    模式(映射键(过滤器#(=mx(val%))amap))]
    (当(<(计数模式)(计数amap))模式时)
    
    在我看来,将某个函数映射到一个集合,然后立即将列表压缩为一个项目是使用
    reduce
    的标志

    (defn tally-map [coll]
      (reduce (fn [h n]
                (assoc h n (inc (h n 0))))
              {} coll))
    
    在本例中,我将编写
    模式
    fn,以将单个集合作为参数,就像您所做的那样。我能想到的对这样的函数使用多个参数的唯一原因是,如果您计划大量键入文字参数

    因此,例如,如果这是一个交互式REPL脚本,并且您经常按字面意思键入
    (模式[1 2 1 2 3])
    ,那么您应该让函数获取多个参数,以避免您键入额外的
    []
    始终在函数调用中。如果您计划从文件中读取大量数字,然后采用这些数字的模式,则让函数采用一个集合参数,这样您就可以避免一直使用
    apply
    。我猜您最常见的用例是后者。我相信
    apply
    还增加了开销,当函数调用采用集合参数时,可以避免这种开销


    我同意其他人的观点,你应该让
    模式
    返回一个结果列表,即使只有一个结果;这会让你的生活更轻松。当你使用时,也许可以将其重命名为
    模式

    这里是
    模式
    的一个很好的简明实现:

    (defn mode [data] 
      (first (last (sort-by second (frequencies data)))))
    
    这充分利用了以下事实:

    • frequencies
      函数返回值->频率的映射
    • 可以将映射视为键值对序列
    • 如果按值对该序列进行排序(每对中的
      第二个
      项),则序列中的最后一项将表示模式
    编辑

    如果要处理多模式情况,则可以插入一个额外的
    分区by
    ,以保持所有值的最大频率:

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

    “您定义的函数实际上是标识函数(因为nil在逻辑上是false)。”不,远不是。比较(map identity[true-false-nil 1])和(map#(not(nil?%1))[true-false-nil 1]的结果)。你是对的,当然,这不是同一个函数。我的意思是说,他可以在这个示例中使用标识函数。我会更正。感谢你的分析和建议。这正是我所寻找的视角的转变。我接受了你的建议,将我的第二个函数重命名为modes.:-(inc(或(hn)0))与(inc(hn0))相同:)哦,对了,我总是忘记默认值选项。谢谢。你是对的,最常见的用例是拉入成堆的数字并弹出结果。谢谢你关于改进理货图的建议。我一直在使用它,任何改进都是值得赞赏的。谢谢你的建议。返回值的方式是这样的
    (defn mode [data] 
      (first (last (sort-by second (frequencies data)))))
    
    (defn modes [data] 
      (->> data
           frequencies 
           (sort-by second)
           (partition-by second)
           last
           (map first)))