【问题标题】:Randomly selecting lines from files从文件中随机选择行
【发布时间】:2010-06-09 20:49:13
【问题描述】:

我有一堆文件,每个文件都有 5 行的标题。在文件的其余部分,一对行构成一个条目。我需要从这些文件中随机选择条目。 如何选择随机文件和随机条目(行对,不包括标题)?

【问题讨论】:

  • 这些文件有多大?
  • 标题是否告诉您文件中的条目数?给定文件中的条目大小是否固定?
  • 每个文件大约有 1000000 个条目。
  • 请发布您正在处理的示例。以及到目前为止您所尝试的。
  • 查看此问题以从文件中选择随机行:stackoverflow.com/questions/232237/…

标签: python perl shell


【解决方案1】:

如果文件足够小,则将成对的行读入内存并从该数据结构中随机选择。如果文件太大,Eugene Y 提供正确答案:使用reservoir sampling

这是算法的直观解释。

Process the file line by line.
pick = line, with probability 1/N, where N = line number

换句话说,在第 1 行,我们将选择具有1/1 概率的第 1 行。在第 2 行,我们会将选择更改到第 2 行,概率为1/2。在第 3 行,我们将选择更改为第 3 行,概率为1/3。等等。

为了直观的证明,想象一个包含 3 行的文件:

        1            Pick line 1.
       / \
     .5  .5
     /     \
    2       1        Switch to line 2?
   / \     / \
 .67 .33 .33 .67
 /     \ /     \
2       3       1    Switch to line 3?

每个结果的概率:

Line 1: .5 * .67     = 1/3
Line 2: .5 * .67     = 1/3
Line 3: .5 * .33 * 2 = 1/3

从那里开始,剩下的就是归纳。例如,假设文件有 4 行。我们已经说服自己,从第 3 行开始,到目前为止的每一行 (1, 2, 3) 都有相同的机会成为我们的当前选择。当我们前进到第 4 行时,它将有1/4 被选中的机会——正是它应该是的,从而将前 3 行的概率减少了恰到好处的数量 (1/3 * 3/4 = 1/4)。

这是Perl FAQ answer,适合您的问题。

use strict;
use warnings;

# Ignore 5 lines.
<> for 1 .. 5;

# Use reservoir sampling to select pairs from remaining lines.
my (@picks, $n);
until (eof){
    my @lines;
    $lines[$_] = <> for 0 .. 1;

    $n ++;
    @picks = @lines if rand($n) < 1;
}

print @picks;

【讨论】:

  • 算法很好的解释。尼特:我认为 OP 说两行构成一个条目,因此您的程序需要稍作修改才能解决此问题(即添加另一个 readline,将 rand() 调用中的行数除以 2)。疯狂的想法:也许可以使用File::Stream,这让您可以在$/ 中使用正则表达式来一次读取两行。当然,这在生产环境中是个坏主意,因为该模块非常慢。
【解决方案2】:

您可能会发现perlfaq5 很有用。

【讨论】:

  • 值得注意的是,它是一个单遍算法,在任何时候只在内存中保留两行。
  • 我会将此作为 CW 发布,因为您(可能)不是此常见问题的作者。
  • +1 聪明的算法。我今天学到了一些东西。 Nit:我想现在,我们应该放弃前面的“srand”。
【解决方案3】:
sed "1,5d" < FILENAME | sort -R | head -2

【讨论】:

  • 有趣。但是“-R”开关在哪里(可能支持“随机化”?我刚刚检查了 Linux(RHEL5.4、coreutils 5.97...)、Mac OS X(10.5.8)和 FreeBSD(6.4)。
【解决方案4】:

Python 解决方案 - 只读取一次文件并且需要很少的内存

像这样调用 getRandomItems(file('myHuge.log'), 5, 2) - 将返回 2 行列表

from random import randrange

def getRandomItems(f, skipFirst=0, numItems=1):
    for _ in xrange(skipFirst):
        f.next()
    n = 0; r = []
    while True:
        try:
            nxt = [f.next() for _ in range(numItems)]
        except StopIteration: break
        n += 1
        if not randrange(n):
            r = nxt
    return r

如果无法从 f 获取第一个可通过的项目,则返回空列表。代码的唯一要求是参数f 是一个迭代器(支持next() 方法)。因此我们可以传递不同于文件的东西,比如我们想查看分布:

>>> s={}
>>> for i in xrange(5000):
...     r = getRandomItems(iter(xrange(50)))[0]
...     s[r] = 1 + s.get(r,0)
... 
>>> for i in s: 
...     print i, '*' * s[i]
... 
0 ***********************************************************************************************
1 **************************************************************************************************************
2 ******************************************************************************************************
3 ***************************************************************************
4 *************************************************************************************************************************
5 ********************************************************************************
6 **********************************************************************************************
7 ***************************************************************************************
8 ********************************************************************************************
9 ********************************************************************************************
10 ***********************************************************************************************
11 ************************************************************************************************
12 *******************************************************************************************************************
13 *************************************************************************************************************
14 ***************************************************************************************************************
15 *****************************************************************************************************
16 ********************************************************************************************************
17 ****************************************************************************************************
18 ************************************************************************************************
19 **********************************************************************************
20 ******************************************************************************************
21 ********************************************************************************************************
22 ******************************************************************************************************
23 **********************************************************************************************************
24 *******************************************************************************************************
25 ******************************************************************************************
26 ***************************************************************************************************************
27 ***********************************************************************************************************
28 *****************************************************************************************************
29 ****************************************************************************************************************
30 ********************************************************************************************************
31 ********************************************************************************************
32 ****************************************************************************************************
33 **********************************************************************************************
34 ****************************************************************************************************
35 **************************************************************************************************
36 *********************************************************************************************
37 ***************************************************************************************
38 *******************************************************************************************************
39 **********************************************************************************************************
40 ******************************************************************************************************
41 ********************************************************************************************************
42 ************************************************************************************
43 ****************************************************************************************************************************
44 ****************************************************************************************************************************
45 ***********************************************************************************************
46 *****************************************************************************************************
47 ***************************************************************************************
48 ***********************************************************************************************************
49 ****************************************************************************************************************

【讨论】:

    【解决方案5】:

    答案在 Python 中。假设您可以将整个文件读入内存。

    #using python 2.6
    import sys
    import os
    import itertools
    import random
    
    def main(directory, num_files=5, num_entries=5):
        file_paths = os.listdir(directory)
    
        # get a random sampling of the available paths
        chosen_paths = random.sample(file_paths, num_files)
    
        for path in chosen_paths:
            chosen_entries = get_random_entries(path, num_entries)
            for entry in chosen_entries:
                # do something with your chosen entries
                print entry
    
    def get_random_entries(file_path, num_entries):
        with open(file_path, 'r') as file:
            # read the lines and slice off the headers
            lines = file.readlines()[5:]
    
            # group the lines into pairs (i.e. entries)
            entries = list(itertools.izip_longest(*[iter(lines)]*2))
    
            # return a random sampling of entries
            return random.sample(entries, num_entries)
    
    if __name__ == '__main__':
        #use optparse here to do fancy things with the command line args
        main(sys.argv[1:])
    

    链接:itertoolsrandomoptparse

    【讨论】:

    • 我在日志所在服务器上的python版本。 2.4.3
    • @EnTerr 如果您去掉空格和 cmets,这与您的答案一样多;这两样你都没有。
    • 呃,将所有行加载到内存中是愚蠢的 - 比如说 50MB 的文件中有 100 万行?
    • @EnTerr,我不会担心 50MB 或一百万行。当我们接近 500MB - 1GB 时,问题可能会开始。因为在任何给定点,内存中最多可能有两个文件内容副本(加上其他变量的一些开销)。
    【解决方案6】:

    另一个 Python 选项;将所有文件的内容读入内存:

    import random
    import fileinput
    
    def openhook(filename, mode):
        f = open(filename, mode)
        headers = [f.readline() for _ in range(5)]
        return f
    
    num_entries = 3
    lines = list(fileinput.input(openhook=openhook))
    print random.sample(lines, num_entries)
    

    【讨论】:

    • 我在日志所在服务器上的python版本。 2.4.3
    【解决方案7】:

    另外两种方法: 1-通过生成器(可能仍然需要大量内存): http://www.usrsb.in/Picking-Random-Items--Take-Two--Hacking-Python-s-Generators-.html

    2- 通过巧妙的寻找(实际上是最好的方法): http://www.regexprn.com/2008/11/read-random-line-in-large-file-in.html

    我在这里复制聪明的 Jonathan Kupferman 的代码:

    #!/usr/bin/python
    
    import os,random
    
    filename="averylargefile"
    file = open(filename,'r')
    
    #Get the total file size
    file_size = os.stat(filename)[6]
    
    while 1:
          #Seek to a place in the file which is a random distance away
          #Mod by file size so that it wraps around to the beginning
          file.seek((file.tell()+random.randint(0,file_size-1))%file_size)
    
          #dont use the first readline since it may fall in the middle of a line
          file.readline()
          #this will return the next (complete) line from the file
          line = file.readline()
    
          #here is your random line in the file
          print line
    

    【讨论】:

      猜你喜欢
      • 2012-03-03
      • 2012-09-03
      • 1970-01-01
      • 2017-03-10
      • 2012-01-10
      • 1970-01-01
      • 2014-07-29
      • 1970-01-01
      相关资源
      最近更新 更多