如何从文本文件中选择一行随机数据

10

我正在尝试为学校(我们有经济体系)制作一款彩票程序。

我的程序会生成数字并将其保存到文本文件中。当我想要从我的生成器中“抽取”数字时,我希望它可以确保有一个获胜者。

问:如何让Python从我的文本文件中选择一行随机数,并将其作为输出结果?

7个回答

18

如何让Python从我的文本文件中随机选择一行并将其输出为该行的内容?

假设文件相对较小,以下可能是最简单的方法:

import random
line = random.choice(open('data.txt').readlines())

12

如果文件非常大,您可以根据文件大小随机查找文件中的某个位置,然后获取下一行完整的内容:

import os, random 
def get_random_line(file_name):
    total_bytes = os.stat(file_name).st_size 
    random_point = random.randint(0, total_bytes)
    file = open(file_name)
    file.seek(random_point)
    file.readline() # skip this line to clear the partial line
    return file.readline()

5
如果你真的希望随机生成器以相同的概率选择每一行,那么这种方法会使较短的行被选中的概率更小,因此不是一个好选择。 - mata
1
它也永远不会返回第一行,并且当random_point在最后一行时将不返回任何行。 - Pascal Hofmann

6
def random_line():
    line_num = 0
    selected_line = ''
    with open(filename) as f:
        while 1:
            line = f.readline()
            if not line: break
            line_num += 1
            if random.uniform(0, line_num) < 1:
                selected_line = line
    return selected_line.strip()

虽然这里提供的大多数方法都可以奏效,但它们往往会一次性将整个文件加载到内存中。但是这种方法不同。因此,即使文件很大,它也能正常工作。
乍一看,这种方法并不是很直观。其背后的定理指出,当我们查看N行时,每个行被选择的概率恰好为1/N。
来自'Python Cookbook'第123页的链接

3

我的第一反应是:

import random
def pick_winner(self):
    lines = []
    with open("file.txt", "r") as f:
        lines = f.readlines();
    random_line_num = random.randrange(0, len(lines))
    return lines[random_lines_num]

3

通过对输入文件进行轻微修改(在第一行存储项目数),您可以选择一个数字而无需先将整个文件读入内存。

import random
def choose_number( frame ):
    with open(fname, "r") as f:
        count = int(f.readline().strip())
        for line in f:
            if not random.randrange(0, count):
                return int(line.strip())
            count-=1

假设你有100个数字。选择第一个数字的概率是1/100。选择第二个数字的概率是(99/100)(1/99)=1/100。选择第三个数字的概率是(99/100)(98/99)(1/98)=1/100。我会跳过正式证明,但选择任何一个数字的几率都是1/100。
在第一行中存储计数并不是严格必要的,但这样做可以避免您必须读取整个文件才能计算行数的麻烦。无论哪种方式,您都不需要将整个文件存储在内存中以等概率选择任何单个行。

2
如果您已经有第一个元素作为行数,那么就不需要为每一行调用random.randrange。只需随机选择行号并前进到该行即可。 - mata

2
另一种方法:
import random, fileinput

text = None
for line in fileinput.input('data.txt'):
    if random.randrange(fileinput.lineno()) == 0:
        text = line
print text

分发:

$ seq 1 10 > data.txt

# run for 100000 times
$ ./select.py > out.txt

$ wc -l out.txt 
100000 out.txt

$ sort out.txt | uniq -c
  10066 1
  10004 10
  10023 2
   9979 3
   9926 4
   9936 5
   9878 6
  10023 7
  10154 8
  10011 9

我没看到偏度,但也许数据集太小了...

这会使选择偏向于文件中出现较早的数字。 - chepner
它的偏差与我预期的略有不同(我没有仔细查看您的代码)。 您基本上是从1到10选择一组数字,然后输出最大的数字。 因此,尽管1被选为集合的一部分的机会更大(实际上,它将始终成为集合的一部分,因为randrange(0,1)将始终返回0),但除非没有选择其他数字,否则永远不会返回1。 请注意,您的分布看起来像一个倒置的钟形曲线,极端数字的选择频率比中间数字高得多。 - chepner
@chepner - 你说的有道理。今天学到了新东西。我想我会花一些时间来尝试一下这个分布。谢谢。 - Fredrik Pihl
1
因此,我计算了一些概率,发现对于选择较小的数字和输出较大的数字的偏差完美地抵消了,因此您应该有1/10的机会输出任何一个数字。例如,选择数字3的几率为1/3,但是不选择更大数字的几率为(3/4)(4/5)...(9/10),两者的乘积正好为1/10。我认为我看到的倒置钟形曲线可能是由于样本量太小造成的。 - chepner
1
+1. 没有偏斜。该算法产生了均匀分布。@chepner: 这是一个著名的水塘抽样算法,k == 1enumerate()可以代替fileinput.lineno() - jfs

-1

我看了一些Python教程,发现了这个片段:

def randomLine(filename):
#Retrieve a  random line from a file, reading through the file once
        fh = open("KEEP-IMPORANT.txt", "r")
        lineNum = 0
        it = ''

        while 1:
                aLine = fh.readline()
                lineNum = lineNum + 1
                if aLine != "":
                        #
                        # How likely is it that this is the last line of the file ? 
                        if random.uniform(0,lineNum)<1:
                                it = aLine
                else:
                        break
        nmsg=it
        return nmsg
        #this is suposed to be a var pull = randomLine(filename)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接