代码之家  ›  专栏  ›  技术社区  ›  clartaq

Clojure中的惯用模式函数

  •  8
  • clartaq  · 技术社区  · 15 年前

    我正在学习Clojure,想听听关于惯用用法的建议。作为一个小型统计软件包的一部分,我有一个函数来计算一组数据的模式。(背景:模式是一组数据中最常见的值。计算模式的算法几乎有十几种。这里使用的是伯纳德·罗斯纳(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)
        )
      )
    

    4 回复  |  直到 15 年前
        1
  •  5
  •   Brian Carper    15 年前

    在我看来,在集合上映射一些函数,然后立即将列表压缩为一个项,这是一个值得使用的标志 reduce .

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

    在这种情况下,我会写 mode fn将单个集合作为参数,就像您所做的那样。我能想到的对这样的函数使用多个参数的唯一原因是,如果您计划大量键入文字参数。

    例如,如果这是一个交互式REPL脚本,而您经常要输入 (mode [1 2 1 2 3]) [] 一直在函数调用中。如果您计划从一个文件中读取大量数字,然后采用这些数字的模式,那么让函数采用一个作为集合的参数,这样您就可以避免使用 apply 总是我猜你最常用的用例是后者。我相信 申请 还增加了在函数调用采用集合参数时避免的开销。

    我同意其他人的看法,你应该这样做 返回结果列表,即使只有一个结果;这会让你的生活更轻松。也许改名吧 modes 当你这么做的时候。

        2
  •  4
  •   Christian Berg    15 年前

    以下是我的看法:

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

    2. 我不会编写在某些情况下返回值而在其他情况下返回值列表的函数,因为调用代码在使用它之前必须检查返回值。相反,我将返回一个单一模式作为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)))
    

    以下是始终返回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)))
    
        3
  •  4
  •   mikera    12 年前

    下面是 mode :

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

    这充分利用了以下事实:

    • 这个 frequencies 函数返回值的映射->频率
    • 可以将映射视为键值对序列
    • 如果按值对该序列进行排序 second 然后序列中的最后一项将表示模式

    编辑

    如果要处理多模式情况,则可以插入额外的 partition-by 要使所有值保持最大频率,请执行以下操作:

    (defn modes [data] 
      (->> data
           frequencies 
           (sort-by second)
           (partition-by second)
           last
           (map first)))
    
        4
  •  2
  •   pmf    15 年前

    我觉得不错。我会换新的

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

    具有

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

    (我不知道为什么会这样 not-nil? 不在 clojure.core ; 这是一个人每天都需要的东西。)

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

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