原題是這樣的:
假設有100G的日誌存在於磁碟之上,其中每條日誌佔用的空間不多於100個位元組,現在從日誌隨機選取N條日誌,保證每條日誌的選取機率相同。
解1:
最簡單且嚴格的做法是掃描日誌兩遍。
第一遍:統計出日誌的總條數,M。
第二遍:掃描每條日誌以N/M的機率選擇該條日誌,直到滿足N條日誌。
解法2:
由於每條日誌的最大長度為100位元組,因此日誌數量至少有K = 1G條,因此可以在掃描日誌的時候進行隨機抽樣,使用一個較大的機率比如 10 * N / K。 這樣選擇出的日誌條數肯定會大於N,並且總量遠小於100G。 接下來可以按第一種方法從樣本中隨機播放出N條日誌。
PS: 解法1和2都比較簡單,但是會存在一個非常嚴重的問題,都需要讀取所有的日誌,100G的資料在讀取時會非常耗時,最理想情況下以廠商的標準最快可以達到80M/s,這樣讀取100G的資料同樣需要1250s,即20多分鐘。那有沒有一種方法減少讀取磁碟的資料量?
解法3:假設檔案的總大小為S=100G,在邏輯上將文本分成B=100W塊,則每個塊的實際大小為K=S/B, 然後在邏輯上遍曆這100W個塊,給每個塊1/1000的選擇機率。這樣按位移量從中選取1000個塊,在這1000個塊中隨機抽取N條日誌。這樣實際讀取的資料量大小為100M, 這已經非常小。
註:第三種方法可以會在每個日誌的選取機率上出現細微的區別,但是由於資料量巨大,故這種差別可以忽略。
從1樓的評論中學習到一個演算法 -- 蓄水池抽樣,原理如下:
定義取出的行號為choice,第一次直接以第一行作為取出行 choice ,而後第二次以二分之一機率決定是否用第二行替換 choice ,第三次以三分之一的機率決定是否以第三行替換 choice ……,以此類推,可用虛擬碼描述如下:
i = 0
while more input lines
with probability 1.0/++i
choice = this input line
print choice
這種方法的巧妙之處在於成功的構造出了一種方式使得最後可以證明對每一行的取出機率都為1/n(其中n為當前掃描到的檔案行數),換句話說對每一行取出的機率均相等,也即完成了隨機的選取。
回顧這個問題,我們可以對其進行擴充,即如何從未知或者很大樣本空間隨機地取k個數?
類比下即可得到答案,即先把前k個數放入蓄水池,對第k+1,我們以k/(k+1)機率決定是否要把它換入蓄水池,換入時隨機的選取一個作為替換項,這樣一直做下去,對於任意的樣本空間n,對每個數的選取機率都為k/n。也就是說對每個數選取機率相等。
虛擬碼:
Init : a reservoir with the size: k
for i= k+1 to N
M=random(1, i);
if( M < k)
SWAP the Mth value and ith value
end for