質問

Clojureを学んでいますが、慣用的な使用法についてのアドバイスをお願いします。小さな統計パッケージの一部として、一連のデータのモードを計算する機能があります。 (背景:モードは一連のデータの中で最も一般的な値です。それを計算するための公開されたアルゴリズムはほぼ12あります。ここで使用されるものは、「生物統計学の基礎」第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)
    )
  )

質問がいくつかあります:

  1. 引数。この関数は単一のシーケンスを受け入れます。加算関数のような可変数の引数を受け入れる方が慣用的ですか?
  2. コードの匂い。 「レット」と思われるあるべきよりも少し複雑です-非常に多くの変数の割り当て。この方法をより簡潔にする言語またはライブラリの明白な(またはそれほど明白ではない)使用を見逃しましたか?

助けてくれてありがとう。

役に立ちましたか?

解決

これが私の見解です:

  1. シーケンスを引数として取るコアclojure関数は多くありますが、他の関数は複数の引数を取るため、私の意見では実際の慣用的な方法はありません。既にシーケンスにデータがある場合は、適用の呼び出しを保存するため、引数としてseqを使用します。

  2. ある場合には値を返し、他の場合には値のリストを返す関数を作成しません。呼び出しコードは使用する前に常に戻り値をチェックする必要があるからです。代わりに、単一のモードを1つのアイテムのみを含む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)))

関数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)))

他のヒント

私の意見では、ある機能をコレクションにマッピングし、リストをすぐに1つのアイテムに凝縮することは、 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 を使用しないようにします。 。あなたの最も一般的なユースケースは後者だと思います。 apply は、コレクション引数を取る関数呼び出しがあるときに回避するオーバーヘッドも追加すると考えています。

結果が1つしかない場合でも、 mode で結果のリストを返すようにする必要があることに、他の人も同意します。それはあなたの人生を楽にします。作業中に modes に名前を変更してください。

mode の簡潔で優れた実装を次に示します。

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

これは次の事実を利用します:

  • frequencies 関数は値のマップを返します-&gt;頻度
  • マップをキーと値のペアのシーケンスとして扱うことができます
  • このシーケンスを値(各ペアの second 項目)でソートすると、シーケンスの最後の項目がモードを表します

編集

複数モードのケースを処理する場合は、追加の partition-by を挿入して、すべての値を最大頻度で保持できます。

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

私には良さそうだ。交換します

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

with

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

not-nilのようなものが clojure.core にない理由がわかりません。毎日必要なものです。)

  

単一の一意のモードがある場合は、それが返されます。複数のモードがある場合、それらはリストとして返されます。モードがない場合、つまりすべての要素が同じ頻度で存在する場合、nilが返されます。&quot;

毎回単純にseqを返すことを考えることができます(1つの要素または空の要素が適切です)。それ以外の場合は、呼び出しコードによってケースを区別する必要があります。常にseqを返すことにより、結果はseqを期待する他の関数の引数として魔法のように機能します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top