从文件中选择随机行

Posted

技术标签:

【中文标题】从文件中选择随机行【英文标题】:Select random lines from a file 【发布时间】:2012-03-03 23:26:02 【问题描述】:

在一个 Bash 脚本中,我想从输入文件中随机抽取 N 行并输出到另一个文件。

如何做到这一点?

【问题讨论】:

对文件进行随机排序并选择N个前行。 另见***.com/questions/12354659/…。 这不是重复的——他想要 N 行 vs 1 行。 相关:Randomly Pick Lines From a File Without Slurping It With Unix 我不同意sort -R,因为它做了很多多余的工作,特别是对于长文件。您可以使用$RANDOM% wc -ljotsed -n(***.com/a/6022431/563329)和 bash 功能(数组、命令重定向等)来定义您自己的 peek 函数,该函数将实际运行在5,000,000 行文件。 【参考方案1】:

使用shuf-n 选项如下所示,得到N 随机行:

shuf -n N input > output

【讨论】:

如果你只需要一组随机的行,而不是随机顺序,那么 shuf 是非常低效的(对于大文件):更好的是进行水库采样,如this answer。跨度> 我在一个 500M 的行文件上运行此程序以提取 1,000 行,耗时 13 分钟。该文件已数月未访问,位于 Amazon EC2 SSD 驱动器上。 所以这本质上比sort -R更随机吗? @MonaJalal nope 更快,因为它根本不需要比较行。 最终是否会多次生成同一行?【参考方案2】:

对文件进行随机排序,首先选择100 行:

lines=100
input_file=/usr/share/dict/words

# This is the basic selection method
<$input_file sort -R | head -n $lines

# If the file has duplicates that must never cause duplicate results
<$input_file sort | uniq        | sort -R | head -n $lines

# If the file has blank lines that must be filtered, use sed
<$input_file sed $'/^[ \t]*$/d' | sort -R | head -n $lines

当然&lt;$input_file 可以替换为任何管道标准输入。这(sort -R$'...\t...'sed 匹配制表符)适用于 GNU/Linux 和 BSD/macOS。

【讨论】:

sort 实际上将相同的行排序在一起,所以如果您可能有重复的行并且您安装了 shuf(一个 gnu 工具),最好使用它。 另外,如果您有一个相当大的文件(80kk 行),这肯定会让您等待很多,而shuf -n 的作用非常迅速。 sort -R 在 Mac OS X (10.9) 下不可用 @tfb785: sort -R 可能是 GNU 选项,安装 GNU coreutils。顺便说一句,shuf 也是 coreutils 的一部分。 @J.F.Sebastian 代码:sort -R input | head -n &lt;num_lines&gt;。输入文件为 279GB,有 2bi+ 行。不过不能分享。无论如何,关键是您可以通过随机播放将 some 行保留在内存中,以随机选择要输出的内容。 Sort 将对整个文件进行排序,无论您需要什么。【参考方案3】:

嗯,根据对 shuf 答案的评论,他在一分钟内改组了 78 000 000 000 行。

接受挑战...

编辑:我打破了自己的记录

powershuf 在 0.047 秒内完成

$ time ./powershuf.py -n 10 --file lines_78000000000.txt > /dev/null 
./powershuf.py -n 10 --file lines_78000000000.txt > /dev/null  0.02s user 0.01s system 80% cpu 0.047 total

之所以这么快,是因为我没有读取整个文件,只是将文件指针移动 10 次,然后打印指针后面的行。

Gitlab Repo

旧尝试

首先我需要一个 78.000.000.000 行的文件:

seq 1 78 | xargs -n 1 -P 16 -I% seq 1 1000 | xargs -n 1 -P 16 -I% echo "" > lines_78000.txt
seq 1 1000 | xargs -n 1 -P 16 -I% cat lines_78000.txt > lines_78000000.txt
seq 1 1000 | xargs -n 1 -P 16 -I% cat lines_78000000.txt > lines_78000000000.txt

这给了我一个包含 780 亿 个换行符的文件 ;-)

现在是 shuf 部分:

$ time shuf -n 10 lines_78000000000.txt










shuf -n 10 lines_78000000000.txt  2171.20s user 22.17s system 99% cpu 36:35.80 total

瓶颈是 CPU 并且没有使用多个线程,它将 1 个核心固定为 100%,其他 15 个未使用。

Python 是我经常使用的,因此我将使用它来加快速度:

#!/bin/python3
import random
f = open("lines_78000000000.txt", "rt")
count = 0
while 1:
  buffer = f.read(65536)
  if not buffer: break
  count += buffer.count('\n')

for i in range(10):
  f.readline(random.randint(1, count))

这让我不到一分钟:

$ time ./shuf.py         










./shuf.py  42.57s user 16.19s system 98% cpu 59.752 total

我在配备 i9 和三星 NVMe 的 Lenovo X1 Extreme 2nd gen 上执行此操作,这为我提供了足够的读写速度。

我知道它可以变得更快,但我会留出一些空间让其他人尝试一下。

线路计数器source: Luther Blissett

【讨论】:

嗯,根据您对powershuf内部功能的描述,看起来只是随机的。使用只有两行的文件,一个是 1 个字符长,另一个是 20 个字符长,我希望两行的选择机会均等。您的程序似乎不是这种情况。 小于 4KB 的文件存在问题,以及其他一些数学错误导致小文件变得可怕。我尽可能地修复了它们,请再试一次。 嗨斯坦。它似乎不起作用。您是否按照我在上述评论中建议的方式对其进行了测试?在制作比 shuf 更快的东西之前,我认为你应该专注于制作与 shuf 一样准确的东西。我真的怀疑任何人都可以用 python 程序击败 shuf。顺便说一句,除非您使用-r 选项,否则 shuf 不会两次输出同一行,当然这需要额外的处理时间。 为什么powershuf会丢弃第一行?它可以选择第一行吗?它似乎也以一种奇怪的方式集中搜索:如果你有 10 行太长,然后是 1 行有效长度,然后是 5 行和另一行有效长度,那么迭代将更频繁地找到 10 行而不是 5 ,并将大约三分之二的时间集中到第一个有效行中。该程序不承诺这一点,但如果这些行按长度有效过滤,然后从该集合中选择随机行,对我来说是有意义的。【参考方案4】:

我的首选选项非常快,我采样了一个制表符分隔的数据文件,它有 13 列、2310 万行、2.0GB 未压缩。

# randomly sample select 5% of lines in file
# including header row, exclude blank lines, new seed

time \
awk 'BEGIN  srand() 
     !/^$/   if (rand() <= .05 || FNR==1) print > "data-sample.txt"' data.txt

# awk  tsv004  3.76s user 1.46s system 91% cpu 5.716 total

【讨论】:

这太棒了——而且速度超级快。【参考方案5】:
seq 1 100 | python3 -c 'print(__import__("random").choice(__import__("sys").stdin.readlines()))'

【讨论】:

【参考方案6】:
# Function to sample N lines randomly from a file
# Parameter $1: Name of the original file
# Parameter $2: N lines to be sampled 
rand_line_sampler() 
    N_t=$(awk 'print $1' $1 | wc -l) # Number of total lines

    N_t_m_d=$(( $N_t - $2 - 1 )) # Number oftotal lines minus desired number of lines

    N_d_m_1=$(( $2 - 1)) # Number of desired lines minus 1

    # vector to have the 0 (fail) with size of N_t_m_d 
    echo '0' > vector_0.temp
    for i in $(seq 1 1 $N_t_m_d); do
            echo "0" >> vector_0.temp
    done

    # vector to have the 1 (success) with size of desired number of lines
    echo '1' > vector_1.temp
    for i in $(seq 1 1 $N_d_m_1); do
            echo "1" >> vector_1.temp
    done

    cat vector_1.temp vector_0.temp | shuf > rand_vector.temp

    paste -d" " rand_vector.temp $1 |
    awk '$1 != 0 $1=""; print' |
    sed 's/^ *//' > sampled_file.txt # file with the sampled lines

    rm vector_0.temp vector_1.temp rand_vector.temp


rand_line_sampler "parameter_1" "parameter_2"

【讨论】:

以上是关于从文件中选择随机行的主要内容,如果未能解决你的问题,请参考以下文章

c# - 将文件中的 10 行随机行(不重复)写入 15 个文件中的每一个

从文件中读取随机行的简单方法是啥?

Clojure 懒惰地从文件中读取随机行

从数据库中选择一个随机行

R:使用 fread 或等价物从文件中读取随机行?

如何从 SQL 数据库表中选择随机行? [复制]