从文件中选择随机行
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 -l
、jot
、sed -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
当然<$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 <num_lines>
。输入文件为 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"
【讨论】:
以上是关于从文件中选择随机行的主要内容,如果未能解决你的问题,请参考以下文章