12

Clojure に手を出している間に、選択肢のリストからランダムな選択肢を選ぶ小さなサンプル プログラムを完成させました。

基本的な考え方は、(重みが割り当てられた) 選択肢を反復処理し、それらの重みを範囲に変換してから、合計範囲内の乱数を選択して 1 つを選択することです。最もエレガントなデザインではないかもしれませんが、当然のこととしましょう。

以下の私の例と比べて何が違うでしょうか?

全体的なプログラム構造の提案、名前の間隔などには興味がありません。主に各機能へのアプローチに興味があります。

私は特に、ベテランの Clojurer が "augment" 関数にどのように取り組むかに興味があります。この関数では、外部の "cur" 変数を使用して範囲の前の端を参照する必要がありました。

  (def colors
      (hash-map 
            :white 1,
            :red 10,
            :blue 20,
            :green 1,
            :yellow 1
       )
     )

    (def color-list (vec colors))

    (def cur 0)

    (defn augment [x] 
      (def name (nth x 0))
      (def val (nth x 1))
      (def newval (+ cur val))
      (def left cur)
      (def right newval)
      (def cur (+ cur val))
      [name left right]
    )

    (def color-list-augmented (map augment color-list))

    (defn within-bounds [bound]
      (def min-bound (nth bound 1))
      (def max-bound (nth bound 2))
      (and (> choice min-bound) (< choice max-bound))
    )

    (def choice (rand-nth (range cur)))

    (def answer 
      (first (filter within-bounds color-list-augmented))
    )

    (println "Random choice:" (nth answer 0))
4

4 に答える 4

8

Clojure を学びながらhttp://www.4clojure.com/でいくつかの問題を解くことをお勧めします。上位ユーザーを「フォロー」して、彼らがどのように問題を解決しているかを確認できます。

これが解決策です。これもまた、最も効率的とは言えません。単純にすることを目指しており、後で学ぶより高度なアイデアや構造を使用しないようにするためです。

user=> (def colors {:white 1  :red 10  :blue 20  :green 1  :yellow 1})
#'user/colors
user=> (keys colors)
(:white :red :blue :green :yellow)   
user=> (vals colors)
(1 10 20 1 1)

重みを間隔に変換するには、累積合計を実行します。

user=> (reductions #(+ % %2) (vals colors))
(1 11 31 32 33)

ランダムな間隔を見つける:

user=> (rand-int (last *1))
13
user=> (count (take-while #(<= % *1 ) *2 ))
2

REPL の注記*1は、印刷された最新の値、次の最新の値などを指します*2。したがって、0 (含む) から 33 (含まない) までのランダムな整数を要求しました。これらの 33 の可能な選択肢は、重みの合計に対応します。次に、その数を見つけるために通過する必要がある間隔の数を数えました。ここでは、乱数は 13 でした。

(1 11 31 32 33) 
     ^ 13 belongs here, 2 numbers in

乱数 2 インチを見つけます。ここに着陸するには、少なくとも 11 で 31 未満でなければならないことに注意してください。つまり、20 の可能性があります。

user=> (nth (keys colors) *1)
:blue

したがって、これをすべて関数にまとめます。

(defn weighted-rand-choice [m]
    (let [w (reductions #(+ % %2) (vals m))
          r (rand-int (last w))]
         (nth (keys m) (count (take-while #( <= % r ) w)))))

テストしてみましょう:

user=> (->> #(weighted-rand-choice colors) repeatedly (take 10000) frequencies)
{:red 3008, :blue 6131, :white 280, :yellow 282, :green 299}
于 2013-01-22T20:15:26.653 に答える
8

Rich Hickey のants.cljからのやや古い (2008) ソリューション:

(defn wrand 
  "given a vector of slice sizes, returns the index of a slice given a
  random spin of a roulette wheel with compartments proportional to
  slices."
  [slices]
  (let [total (reduce + slices)
        r (rand total)]
    (loop [i 0 sum 0]
      (if (< r (+ (slices i) sum))
        i
        (recur (inc i) (+ (slices i) sum))))))

data.generators からのStuart Hallowayのより最近 (2012 年) のソリューション:

(defn weighted
  "Given a map of generators and weights, return a value from one of
  the generators, selecting generator based on weights."
  [m]
  (let [weights   (reductions + (vals m))
        total   (last weights)
        choices (map vector (keys m) weights)]
    (let [choice (uniform 0 total)]
      (loop [[[c w] & more] choices]
        (when w
          (if (< choice w)
            (call-through c)
            (recur more)))))))
于 2013-04-04T22:52:05.223 に答える
4

多くの場合、問題を個別に解決できるレイヤーに分割するのに役立ちます。Augmentは範囲を割り当てる際にこれをうまく行いますが、ランダムに1つを選択する場合、通常のシーケンス関数では結果を消費するのが難しくなります。通常のシーケンスを生成するように拡張の目標を変更すると、拡張の問題は、ランダムに選択することからより明確に分離されます。重みが整数の場合、各アイテムの重み番号を含むリストを作成し、ランダムに1つを選択できます。

user> (map (fn [[item weight]] (repeat weight item)) colors)
((:white) 
 (:red :red :red :red :red :red :red :red :red :red) 
 (:blue :blue :blue :blue :blue :blue :blue :blue :blue :blue
  :blue :blue :blue :blue :blue :blue :blue :blue :blue :blue) 
 (:green) (:yellow)) 

次に、それを1つのリストにまとめます。

user> (flatten (map (fn [[item weight]] 
                       (repeat weight item)) 
                 colors))
(:white :red :red :red :red :red :red :red :red :red :red 
 :blue :blue :blue :blue :blue :blue :blue :blue :blue :blue 
 :blue :blue :blue :blue :blue :blue :blue :blue :blue :blue 
 :green :yellow)

と1つを選択してrand-nthください:

user> (rand-nth (flatten (map (fn [[item weight]] (repeat weight item)) colors)))
:blue

ps:マップリテラルは物事をより良く見せます:リーダーページはこれらをうまく説明しています

(def colors {:white 1,
             :red 10,
             :blue 20,
             :green 1,
             :yellow 1})

関数内で物事に名前を付けるには、letを使用します。

(defn augment [x]
  (let [name (nth x 0)
        val (nth x 1)
        newval (+ cur val)
        left cur
        right newval
        cur (+ cur val)]
    [name left right])) 
于 2013-01-22T18:08:50.677 に答える
0

オープン ソースのbigmlサンプリング ライブラリも別のオプションです。私はそれを使ってある程度成功しました。それははるかによく文書化されており、優れた API を備えています。

于 2015-08-29T21:11:16.187 に答える