ランダム再生はいいのだが,このスクリプトを見た Muraoka 師が問題点に気づいた.
bash スクリプトの中では乱数として組み込み変数の RANDOM が使える.今回使った Jonas Klang 氏のスクリプトでも RANDOM を使っている.あまりよい疑似乱数でもないらしいが,それ以前に,この変数は 32K までの整数を返す.
では,自分のライブラリにはどのくらいの曲数があるのかというと,これは
mpc stats
で調べることができて,現時点で 41,000 を超えている.ということは,1万曲近くがそもそも再生の対象にならない.ということで,スクリプトを修正してみた.
もとのスクリプトでは mpc listall コマンドで出てくる曲のリストから選び出しているので,フルパスをアルファベット順に並べて上位 32K から選ばれる.つまり,Zabadak などは絶対に出てこないw
となると,32K を超える範囲で乱数を得る必要がある.Unix では乱数といえば,/dev/random ないし /dev/urandom からもらってくるものらしい.
具体的には以下のようにしてみた.
終わりの方にある addSong() の中の
song=`mpc listall | sed -n $[RANDOM % $(mpc stats | grep Songs | awk '{print $2}')+1]p`
という行でランダムに曲を決めている.
mpc listall で出てくる曲リストから,ランダムに 1行 sed で拾うのだが,乱数を全曲数 (mpc stats で取得する) で割った余り (+1) を番号としているわけだ.
最初は,RANDOM の部分を
mpc listall で出てくる曲リストから,ランダムに 1行 sed で拾うのだが,乱数を全曲数 (mpc stats で取得する) で割った余り (+1) を番号としているわけだ.
最初は,RANDOM の部分を
$(( $(od -vAn -N4 -tu4 < /dev/random) % 100000 ))
に置き換えるのを試してみた.これはまず 4バイト整数の乱数を得て,それを 100,000 で割ることで 100,000 までの乱数にしている.
これはこれでちゃんと動いたのだが,別に 100,000 に制限する意味もないので,もっと短くこれでいいのではないか.
song=`mpc listall | sed -n $[ $(od -vAn -N4 -tu4 < /dev/random) % $(mpc stats | grep Songs | awk '{print $2}')+1]p`
というか,こうでないとまずいのか.
たとえば,1~5 の整数を出したいとする.1~10までの乱数を出して,5で割った余りを見る.この場合は0から4までの余りの出る確率は等確率.しかし,1~11までの乱数だったらどうか.余り1の出る場合が,1,6,11の3通りあり,2通りしか出現パターンのない他の余りより高い確率,たとえば余り2の出現する確率の 1.5 倍で出現することになる.
一方,1~101 までの乱数を5で割ってとやれば,余り1は11通り,他の余りは10通りなので,違いは10% .つまり,元の乱数範囲を,最終的に出したい乱数範囲よりも十分に大きく取ってやれば,こういう問題は発生しなくなる.元の乱数範囲をA,出したい範囲をBとすると,A ÷ B の余りまでの数は,それ以上の数より1回多く出現パターンがある.出現パターンの数は,余りの数より大きい数については A ÷ B の商である.だから,A ÷ B が1より十分に大きければ,1回のパターン増加は無視できることになる.
さて,4バイト整数 = 32ビット整数 だから,要するに 4G までの整数ということになる (2^10 = 1K を覚えておくと便利).一方,今の曲数は 40K くらい.これが増え続けて,仮に 100K まで行ったとしよう.そうすると,4G / 100K = 4M / 100 = 40K.このくらいなら1増えても無視してよいだろう.
一方,あらかじめ10万までに制限してしまうと,曲数が4万だと,2万までは3通りの出現パターンがあるのに,2万以上の数は2通りしか出現パターンがないので,後半の曲が出てくる確率が前半の2/3になってしまい,違いが無視できない.
というような実装をしたら,ようやく Watanabe, Sadao 辺りも出てくるようになった.
で,いいのかな.どっか間違ってるかもしれないw
/dev/random 使っているとはいえ,それはあくまでも長い目で見た一様性だから,短期的には偏ってくることはあるって感じはするなあ.昨日かかった曲がまたリストに挙がってきてる.
以下,改造版のスクリプト
たとえば,1~5 の整数を出したいとする.1~10までの乱数を出して,5で割った余りを見る.この場合は0から4までの余りの出る確率は等確率.しかし,1~11までの乱数だったらどうか.余り1の出る場合が,1,6,11の3通りあり,2通りしか出現パターンのない他の余りより高い確率,たとえば余り2の出現する確率の 1.5 倍で出現することになる.
一方,1~101 までの乱数を5で割ってとやれば,余り1は11通り,他の余りは10通りなので,違いは10% .つまり,元の乱数範囲を,最終的に出したい乱数範囲よりも十分に大きく取ってやれば,こういう問題は発生しなくなる.元の乱数範囲をA,出したい範囲をBとすると,A ÷ B の余りまでの数は,それ以上の数より1回多く出現パターンがある.出現パターンの数は,余りの数より大きい数については A ÷ B の商である.だから,A ÷ B が1より十分に大きければ,1回のパターン増加は無視できることになる.
さて,4バイト整数 = 32ビット整数 だから,要するに 4G までの整数ということになる (2^10 = 1K を覚えておくと便利).一方,今の曲数は 40K くらい.これが増え続けて,仮に 100K まで行ったとしよう.そうすると,4G / 100K = 4M / 100 = 40K.このくらいなら1増えても無視してよいだろう.
一方,あらかじめ10万までに制限してしまうと,曲数が4万だと,2万までは3通りの出現パターンがあるのに,2万以上の数は2通りしか出現パターンがないので,後半の曲が出てくる確率が前半の2/3になってしまい,違いが無視できない.
というような実装をしたら,ようやく Watanabe, Sadao 辺りも出てくるようになった.
で,いいのかな.どっか間違ってるかもしれないw
/dev/random 使っているとはいえ,それはあくまでも長い目で見た一様性だから,短期的には偏ってくることはあるって感じはするなあ.昨日かかった曲がまたリストに挙がってきてる.
以下,改造版のスクリプト
#!/bin/bash # stdin check if (( "$#" < 2 )); then echo "Usage: `basename $0` HOSTNAME MIN_PLAYLIST_LENGTH [NEXT_ON_ERROR (y/N)]" exit 65 fi # variables host=$1 length=$2 state_file="$host.state" export MPD_HOST=$host if [ "$3" ]; then cont=${3^^} cont=${cont:0:1} else cont="N" fi # go go go echo "Mpd_Add v.0.6a" echo "Orignally by Jonas Klang, modified by yjo" echo "Host: $host / Playlist length: $length" echo "Next on error: $cont" # loop loop () { while true; do if `mpc version 2>/dev/null | grep -q 'mpd version:'`; then setState while (( `mpc playlist | grep -c ^` < $length )); do addSong sleep 1 done mpc idle &> /dev/null else sleep 30 fi done } # output current status to file # does conversion from utf8 to iso-8859-15 for compatability #+with samurize on windows setState() { if `mpc | grep -q '\[playing\]'`; then mpc --format "%artist% (%date%) %album% / %track% / %title%" current | iconv -s -c -f UTF-8 -t iso-8859-15 -o $state_file elif `mpc | grep -q '\[paused\]'`; then echo "pause" > $state_file if `mpc | grep -q 'single: on'`; then mpc -q single off && mpc -q stop && echo "not playing" > $state_file fi else if [ $cont = "Y" ]; then mpc -q play else echo "not playing" > $state_file fi fi } # add one random song from mpd library addSong () { song=`mpc listall | sed -n $[ $(od -vAn -N4 -tu4 < /dev/random) % $(mpc stats | grep Songs | awk '{print $2}')+1]p` if [ "$song" ]; then mpc -q add "$song" fi } # backgrounds the loop and exits main thread loop & exit 0
0 件のコメント:
コメントを投稿