[インデックス 1134] ファイルの概要
このコミットは、Go言語の標準ライブラリであるrand
パッケージに、指定された範囲の整数をランダムに並べ替える(順列を生成する)機能を追加するものです。具体的には、perm(n int) *map[int]int
という関数が導入され、0からn-1
までの整数を要素とするマップをランダムにシャッフルした結果を返します。これは、ランダムなサンプリングや要素の順序を無作為化する際に利用される基本的な機能です。
コミット
commit 2567c073ea230cf96078477be117f07a16e93470
Author: Ken Thompson <ken@golang.org>
Date: Sun Nov 16 13:02:47 2008 -0800
random permutation function
func perm(n int) *map[int]int
R=r
OCL=19340
CL=19340
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2567c073ea230cf96078477be117f07a16e93470
元コミット内容
このコミットの元々の内容は、Go言語のrand
パッケージに「ランダムな順列関数」を追加するというものです。具体的には、perm(n int) *map[int]int
というシグネチャを持つ関数が導入され、これはn
個の要素のランダムな順列を生成することを目的としています。コミットメッセージには、Go言語の初期開発における内部的なレビュープロセスを示すR=r
(Reviewed by r)や、変更リスト番号であるOCL
(Original Change List)とCL
(Change List)が含まれています。
変更の背景
Go言語の初期段階において、標準ライブラリは基本的な機能から順次整備されていました。乱数生成は多くのアプリケーションで必要とされる基本的な機能であり、単に乱数を生成するだけでなく、コレクションの要素をランダムに並べ替えたり、特定の範囲の数値のランダムな順列を得たりする機能も重要です。
このperm
関数の追加は、以下のような背景が考えられます。
- 基本的なユーティリティの提供: プログラミングにおいて、配列やリストの要素をランダムにシャッフルする、あるいは特定の範囲の数値のランダムな順列を生成するニーズは非常に一般的です。このような基本的なユーティリティを標準ライブラリとして提供することで、開発者が独自に実装する手間を省き、より堅牢で効率的なコードを書けるようにすることが目的です。
- 統計的サンプリングやシミュレーション: 統計的なサンプリング、モンテカルロシミュレーション、ゲーム開発など、ランダムな順序付けが必要な多くのシナリオで、この関数は基盤となります。
- Go言語の設計思想: Go言語は「シンプルさ」と「実用性」を重視しており、よく使われる機能は標準ライブラリに含めるという設計思想があります。
perm
関数もその一環として追加されたと考えられます。
前提知識の解説
1. ランダムな順列(Random Permutation)
ランダムな順列とは、ある集合の要素をすべて使用して、それらをランダムな順序で並べ替えたものです。例えば、集合 {0, 1, 2}
のランダムな順列には、{0, 1, 2}
、{0, 2, 1}
、{1, 0, 2}
、{1, 2, 0}
、{2, 0, 1}
、{2, 1, 0}
などがあります。すべての可能な順列が等しい確率で生成されることが、真に「ランダムな」順列であるための条件です。
2. Go言語のmap
型
Go言語のmap
は、キーと値のペアを格納するハッシュテーブル(連想配列)です。map[KeyType]ValueType
のように宣言され、KeyType
は比較可能な型(整数、文字列など)、ValueType
は任意の型です。このコミットで使われているmap[int]int
は、整数をキーとし、整数を値とするマップを意味します。
3. Fisher-Yatesシャッフルアルゴリズム(またはKnuthシャッフル)
Fisher-Yatesシャッフルは、有限集合の要素をランダムに並べ替えるためのアルゴリズムです。このアルゴリズムは、すべての可能な順列を等しい確率で生成するという点で、統計的に公平なシャッフルを提供します。
基本的な手順は以下の通りです(配列a
の要素をシャッフルする場合):
- 配列の最後の要素から最初の要素まで(またはその逆)を順に見ていく。
- 現在の要素
i
について、0
からi
までのランダムなインデックスj
を選択する。 - 要素
a[i]
とa[j]
を交換する。
このコミットのperm
関数は、このFisher-Yatesシャッフルアルゴリズムの変形をmap
に対して適用しているように見えます。
技術的詳細
perm
関数は、n
という整数を受け取り、0
からn-1
までの整数をキーと値に持つmap[int]int
のポインタを返します。このマップは、初期状態ではm[i] = i
という形で、キーと値が一致するエントリがn
個格納されています。
関数の実装は以下のステップで行われます。
-
m := new(map[int]int);
map[int]int
型の新しいマップを生成し、そのポインタをm
に代入します。Go言語のマップは参照型ですが、ここではnew
を使ってポインタとして扱っています。これはGo言語の初期のコードスタイルや、特定のメモリ管理の意図があった可能性があります。現代のGoでは通常make(map[int]int)
を使用します。
-
for i:=0; i<n; i++ { m[i] = i; }
0
からn-1
までの各整数i
に対して、マップm
にi
をキーとし、i
を値とするエントリを追加します。これにより、マップは{0:0, 1:1, ..., n-1:n-1}
という初期状態になります。
-
for i:=0; i<n; i++ { j := nrand(n); t := m[i]; m[i] = m[j]; m[j] = t; }
- このループが、マップの値をランダムにシャッフルする核心部分です。
i
は0
からn-1
まで順に進みます。j := nrand(n);
nrand(n)
は、0
以上n
未満のランダムな整数を生成する関数です。これはrand
パッケージ内の別の関数であり、このコミットの差分には含まれていませんが、乱数生成の基盤となります。j
は、0
からn-1
までのランダムなインデックス(キー)として選ばれます。
t := m[i]; m[i] = m[j]; m[j] = t;
- これは、マップのキー
i
に対応する値m[i]
と、ランダムに選ばれたキーj
に対応する値m[j]
を交換する操作です。 - 一時変数
t
を使って値を保持し、m[i]
にm[j]
の値を、m[j]
にt
(元のm[i]
の値)を代入しています。
- これは、マップのキー
このシャッフルアルゴリズムは、Fisher-Yatesシャッフルに非常に似ています。ただし、一般的なFisher-Yatesシャッフルが配列の要素を交換するのに対し、ここではマップの値を交換しています。キーは0
からn-1
まで固定されており、それに対応する値がランダムに並べ替えられることで、結果的に0
からn-1
までの数値のランダムな順列がマップの値として得られます。
例えば、perm(3)
を呼び出した場合:
初期状態: m = {0:0, 1:1, 2:2}
-
i = 0
:j
がランダムに選ばれる(例:j = 2
)m[0]
(値は0) とm[2]
(値は2) を交換m
は{0:2, 1:1, 2:0}
になる
-
i = 1
:j
がランダムに選ばれる(例:j = 0
)m[1]
(値は1) とm[0]
(値は2) を交換m
は{0:1, 1:2, 2:0}
になる
-
i = 2
:j
がランダムに選ばれる(例:j = 2
)m[2]
(値は0) とm[2]
(値は0) を交換(変化なし)m
は{0:1, 1:2, 2:0}
になる
最終的に返されるマップの値は、{1, 2, 0}
という0, 1, 2
のランダムな順列になります。このアルゴリズムは、すべての順列を等しい確率で生成するため、統計的に公平な結果が得られます。
コアとなるコードの変更箇所
src/lib/rand.go
ファイルに以下の変更が加えられました。
--- a/src/lib/rand.go
+++ b/src/lib/rand.go
@@ -14,6 +14,7 @@ package rand
// urand32 - return random uint32
// nrand, nrand31, nrand63 - return 0 <= random < n
// frand, frand64, frand32 - return 0 <= random float, float64, float32 < 1
+// perm gives a random permutation map[int]int
const
(
@@ -162,6 +163,22 @@ frand() float
return float(frand64())
}
+export func
+perm(n int) *map[int]int
+{
+ m := new(map[int]int);
+ for i:=0; i<n; i++ {
+ m[i] = i;
+ }
+ for i:=0; i<n; i++ {
+ j := nrand(n);
+ t := m[i];
+ m[i] = m[j];
+ m[j] = t;
+ }
+ return m;
+}
+
func
init()
{
コアとなるコードの解説
変更は主にsrc/lib/rand.go
ファイルに集中しており、以下の2つの主要な追加があります。
-
コメントの追加:
// perm gives a random permutation map[int]int
既存の乱数生成関数の説明コメント群に、
perm
関数がmap[int]int
形式でランダムな順列を提供することを示す行が追加されました。これにより、この関数の目的が明確になります。 -
perm
関数の実装:export func perm(n int) *map[int]int { m := new(map[int]int); for i:=0; i<n; i++ { m[i] = i; } for i:=0; i<n; i++ { j := nrand(n); t := m[i]; m[i] = m[j]; m[j] = t; } return m; }
export func perm(n int) *map[int]int
:export
キーワードは、この関数がパッケージ外からアクセス可能であることを示します。Go言語の初期の構文であり、現在のGoでは関数名が大文字で始まることでエクスポートされます(例:Perm
)。perm
は関数名。n int
は、順列を生成する要素の数(0からn-1
までの範囲)を指定する整数型の引数。*map[int]int
は、整数をキーとし、整数を値とするマップへのポインタを戻り値とすることを示します。
m := new(map[int]int);
:- 新しい空の
map[int]int
を生成し、そのポインタをm
に代入します。
- 新しい空の
for i:=0; i<n; i++ { m[i] = i; }
:- 最初のループでは、マップ
m
を初期化します。i
が0
からn-1
まで増加するにつれて、m[0]=0, m[1]=1, ..., m[n-1]=n-1
というエントリが作成されます。これにより、マップは順序付けられた状態になります。
- 最初のループでは、マップ
for i:=0; i<n; i++ { ... }
:- 2番目のループがシャッフル処理を行います。
i
は0
からn-1
まで順に進みます。 j := nrand(n);
:0
からn-1
までの範囲でランダムな整数j
を生成します。このj
は、交換対象となるマップのキー(インデックス)として使用されます。t := m[i]; m[i] = m[j]; m[j] = t;
:- これは典型的な値の交換(スワップ)操作です。
m[i]
の現在の値を一時変数t
に保存します。m[j]
の値をm[i]
にコピーします。t
に保存しておいた元のm[i]
の値をm[j]
にコピーします。- これにより、
m[i]
とm[j]
の値が交換されます。
- 2番目のループがシャッフル処理を行います。
この実装は、Fisher-Yatesシャッフルアルゴリズムの「内側から外側へ」のバリアントに似ていますが、ループの範囲とランダムなインデックスの選択方法が若干異なります。しかし、結果としてすべての順列が等しい確率で生成されるという特性は維持されています。
関連リンク
- Go言語の
math/rand
パッケージ(現代のGoにおける乱数生成の標準パッケージ): https://pkg.go.dev/math/rand - Fisher-Yatesシャッフル - Wikipedia: https://ja.wikipedia.org/wiki/フィッシャー=イェーツのシャッフル
参考にした情報源リンク
- Go言語の公式ドキュメント
- Fisher-Yatesシャッフルに関する一般的なアルゴリズム解説
- Go言語の
map
型に関するドキュメント - Go言語の初期のソースコードとコミット履歴(GitHub)