これを 1 回のパスで解決するには、Reservoir Samplingを使用します。
リストの長さが事前にわからない項目のリストから 1 つ以上の項目をランダムに選択する場合は、Reservoir Samplingを使用できます。
これを (メモリ内のすべての行のバッファリングを回避する) メソッドと共に利用して、File.ReadLines()
バッファリングせずに各行を 1 回だけ読み取るシングルパス アルゴリズムを作成できます。
以下のサンプル コードは、任意の数の行をランダムに選択できる一般化されたソリューションを示しています。あなたの場合、N = 1です。
サンプル コードには、行が一様分布でランダムに選択されることを証明するテスト プログラムも含まれています。
(このコードがどのように機能するかを確認するには、上でリンクした Wiki 記事を参照してください。)
using System;
using System.IO;
using System.Collections.Generic;
namespace Demo
{
internal class Program
{
public static List<string> RandomlyChooseLinesFromFile(string filename, int n, Random rng)
{
var result = new List<string>(n);
int index = 0;
foreach (var line in File.ReadLines(filename))
{
if (index < n)
{
result.Add(line);
}
else
{
int r = rng.Next(0, index + 1);
if (r < n)
result[r] = line;
}
++index;
}
return result;
}
// Test RandomlyChooseLinesFromFile()
private static void Main(string[] args)
{
Directory.CreateDirectory("C:\\TEST");
string testfile = "C:\\TEST\\TESTFILE.TXT";
File.WriteAllText(testfile, "0\n1\n2\n3\n4\n5\n6\n7\n8\n9");
var rng = new Random();
int trials = 100000;
var counts = new int[10];
for (int i = 0; i < trials; ++i)
{
string line = RandomlyChooseLinesFromFile(testfile, 1, rng)[0];
int index = int.Parse(line);
++counts[index];
}
// If this algorithm is correct, each line should be chosen
// approximately 10% of the times.
Console.WriteLine("% times each line was chosen:\n");
for (int i = 0; i < 10; ++i)
{
Console.WriteLine("{0} = {1}%", i, 100*counts[i]/(double)trials);
}
}
}
}