これは、サンプルごとにファイルを 1 回だけ通過させる単純なソリューションです。ファイルからサンプリングするアイテムの数が正確にわかっている場合は、おそらく最適です。
まずはサンプル関数です。これは、以前の回答のコメントで@NedBatchelderがリンクしたのと同じアルゴリズムを使用します(ただし、そこに示されているPerlコードは、複数ではなく1行のみを選択しました)。反復可能な行から値を選択し、現在選択されている行だけを常にメモリに保持する必要があります (および次の候補行)。ValueError
iterable の値が要求されたサンプル サイズよりも少ない場合は、 a が発生します。
import random
def random_sample(n, items):
results = []
for i, v in enumerate(items):
r = random.randint(0, i)
if r < n:
if i < n:
results.insert(r, v) # add first n items in random order
else:
results[r] = v # at a decreasing rate, replace random items
if len(results) < n:
raise ValueError("Sample larger than population.")
return results
編集:別の質問で、ユーザー @DzinX は、非常に多数の値をサンプリングしている場合insert
、このコードで を使用するとパフォーマンスが低下することに気付きました ( )。O(N^2)
その問題を回避する彼の改良版はこちらです。/編集
次に、関数がサンプリングする適切な iterable アイテムを作成する必要があります。ジェネレーターを使用してそれを行う方法は次のとおりです。このコードは、一度に 1 つのファイルしか開いたままにせず、一度に複数のメモリ行を必要としません。オプションのexclude
パラメーターが存在する場合はset
、以前の実行で選択された行を含む必要があります (したがって、再度生成されるべきではありません)。
import os
def lines_generator(base_folder, exclude = None):
for dirpath, dirs, files in os.walk(base_folder):
for filename in files:
if filename.endswith(".txt"):
fullPath = os.path.join(dirpath, filename)
with open(fullPath) as f:
for line in f:
cleanLine = line.strip()
if exclude is None or cleanLine not in exclude:
yield cleanLine
ここで必要なのは、これら 2 つの部分を結び付ける (そして表示された一連の行を管理する) ラッパー関数だけです。ランダム サンプルからのスライスもランダム サンプルであるという事実を利用して、サイズの 1 つのサンプルn
またはサンプルのリストを返すことができます。count
_seen = set()
def get_sample(n, count = None):
base_folder = r"C:\Tasks"
if count is None:
sample = random_sample(n, lines_generator(base_folder, _seen))
_seen.update(sample)
return sample
else:
sample = random_sample(count * n, lines_generator(base_folder, _seen))
_seen.update(sample)
return [sample[i * n:(i + 1) * n] for i in range(count)]
使用方法は次のとおりです。
def main():
s1 = get_sample(10)
print("Sample1:", *s1, sep="\n")
s2, s3 = get_sample(10,2) # get two samples with only one read of the files
print("\nSample2:", *s2, sep="\n")
print("\nSample3:", *s3, sep="\n")
s4 = get_sample(5000) # this will probably raise a ValueError!