[UEFN][Verse]配列のシャッフル[1]「乱数を重複無しで生成し、配列に格納する」アルゴリズムをVerselangで書いてみる
公式フォーラムで日本語で質問している方がいて、その内容が興味深かったので考えてみました。
スレッドの主旨は「乱数を重複無しで生成し、配列に格納する」という物です。これは例えば「52枚のトランプからランダムに3枚選ぶ」というような処理で必要になる考え方です。
わかりやすさの為に、ここでは「1から52まで書かれたカードから任意枚数を選びかつ並び順をランダムにする」という処理をイメージします。「ランダムに複数の要素を選択し、かつ、同じ要素は選択しない」という点がポイントです。
投稿者さんは以下の様なJavaScriptコードを挙げ、これをVerseにコンバートするにはどうやるのかを尋ねていました。
function randomizing(){
var arr = [];
numArr = [];
//1
for(var i=0; i < 5; i++){
arr[i]=i+1;
}
//2
for(var j = 0, var len = arr.length; j < 5; j++, len--) {
//3
rndNum = Math.floor(Math.random()*len);
numArr.push(arr[rndNum]);
//4
arr[rndNum] = arr[len-1];
}
}
アルゴリズムについて不勉強なこともあり、最初コードがしている処理の意味がわからりませんでした。折角なので多少詳しく解説しておきます。なお、サンプルコードな事もあり、この実装だと関数が値を返しませんが、その辺はニュアンスで対処しましょう。
//1 元の値の集合を連番で生成しています。具体的にはarr配列には[1,2,3,4,5]が格納されています。例えばここでi<52
としておけばarr配列には1~52の値が格納されます。
//2 出力となるnumArr配列を生成するforループです。j < 5
がnumArr配列の要素数を定義していて、ここでは要素数5の配列が生成されます。
lenは、arr配列内でアクセスを許す末端のインデックスを示します。ループ開始時は配列の終端を示し、ループのたびにデクリメントされます。このlenの挙動がポイントです。
//3 乱数を生成してarr配列から値を取得し、出力となるrndNum配列の末尾に追加します。生成する乱数は0からlenまでで、ループ開始時にはarr配列全体が取得の対象範囲となり、以降イテレーション毎に末尾から1要素ずつ減っていきます。
//4 //3で参照した配列要素はもう不要なので、arr配列でアクセスが許される範囲の末端要素の値をそこにコピーします。重なってるカードから1枚引き抜き、一番後ろにあるカードを抜いた場所に移動しているイメージです。
コード上ではコピー元が残っていますが、次のループでは(lenがデクリメントされるため)そこにはもうアクセスされません。このような仕組みで、同じ要素を選択しないようにしているのです。
Verseへのコンバート
では、このコードをVerseにコンバートしてみましょう。
Verseの流儀に合わせて変数名などを刷新していますが、出来るだけ元コードのアルゴリズムを踏襲しているつもりです。
Randomizing(NumberOfSheets:int, Hands:int ):[]int =
var Deck :[]int= for:
CardNumber:=1..NumberOfSheets
do:
CardNumber
for:
Number := 1..Hands
PickTerminator := Deck.Length - Number
RandomPick := GetRandomInt(0, PickTerminator)
Result := Deck[RandomPick]
set Deck[RandomPick] = Deck[PickTerminator]
do:
Result
#output
for:
Index->Result:Randomizing(52, 5)
do:
Print("{Index} : {Result}")
Randomizingはint配列を返します。このint配列はFor式が生成します。VerseのFor式は、各イテレーションごとに最後に評価した値からなる配列を返します。JavaScript版と随分見た目が異なりますが、振る舞いは同じです。
続く(かもしれない)
本当はこの後に配列シャッフル(フィッシャー・イェーツのシャッフル)に特化したVerseコードを書いてみるつもりだったんですが、いざやろうとしたら上手くいかず、早々に諦めてしまいました。
というわけで、配列シャッフルのコードの書き方が思いついたら続きます。関数型言語に向いた配列シャッフルアルゴリズムがあるのかな……?
続き
思いついたので書きました。
お知らせ
verse言語とUEFNの記事を他にも書いているので御覧下さい。
最後まで読んで頂きありがとうございました。この記事がお役に立てたようであれば、是非LIKEとフォローをお願いします(今後の執筆のモチベーションに繋がります)。
#Verse #UEFN #Fortnite #Verselang #UnrealEngine
宣伝
「Unityシェーダープログラミングの教科書」シリーズ1~5をBOOTHで頒布中です。
Discussion