【问题标题】:How to efficiently get 10% of random lines out of the large file in Linux?如何有效地从 Linux 中的大文件中获取 10% 的随机行?
【发布时间】:2014-02-13 12:42:05
【问题描述】:

我想随机输出文件总行数的 10%。例如,文件 a 有 1,000,000 行,然后我想从文件中随机输出 100,000 行(100,000 是 1,000,000 的 10%)。

假设文件很小,这很容易做到:

randomLine=`wc -l a | awk '{printf("%d\n",($1/10))}'`
sort -R a | head -n $randomLine

但是使用 sort -R 非常慢。它将执行专门的随机计算。我的文件有 10,000,000 行。排序需要太多时间。有没有办法归档一个不那么专门、不那么随机但有效的采样?

编辑想法:

  1. 每十行采样一行是可以接受的。但我不知道如何使用 shell 脚本来做到这一点。
  2. 逐行读取,如果

    echo $RANDOM%100 | bc
    

大于 20 则输出该行(使用大于 10 的数字确保得到不少于 10% 的行),一旦输出 10% 行就停止。但我不知道如何使用 shell 脚本逐行读取。

编辑说明

我想使用 shell 脚本的原因是我的文件包含 \r 字符。文件中的换行符应该是 \n 但是 Python 和 Java 中的 readline() 函数将 \r 和 \n 视为换行符,这不符合我的需要。

【问题讨论】:

  • 1000 不是 1,000,000 的 10% :|
  • 是否足够随机以从每 10 行中随机打印一行?
  • 如果您正在寻找总体思路;看看水库采样。 (不知道如何将其改编为 shell 脚本)。
  • 谢谢。 1,000,000 的 10% 是 100,000!
  • 这不应该在 shell 中完成。如果您坚持,请逐行阅读,每次都会得到一个(均匀)分布的随机数。选择一个阈值,使 90% 的随机数低于该阈值(可能是某个模数 m)。仅当随机数超过阈值时才打印每一行。 (如果您需要恰好 10%,请在 [1...行数] 上进行分配,其中 90% 的行低于阈值...您不想在 shell 中这样做)

标签: linux algorithm bash sorting random


【解决方案1】:

让我们创建一个从 1 到 Y 的 X 数字的随机列表。您可以这样做:

shuf -i 1-Y -nX

在你的情况下,

shuf -i 1-1000000 -n10000

然后将其存储在一个变量中(空格分隔)并传递给awk,以便打印这些行号:

awk 'FNR==NR {a[$1]; next} {if (FNR in a) print}' <(shuf -i 1-1000000 -n10000) file

说明

  • FNR==NR {a[$1]; next} 循环遍历 shuf 结果并将它们存储在 a[] 数组中。
  • {if (FNR in a) print} 如果在数组a[] 中找到第二个参数(file)的行号,则打印它。

Y=10,X=2 的示例

$ cat a
1 hello
2 i am
3 fe
4 do
5 rqui
6 and
7 this
8 is 
9 sample
10 text

$ awk 'FNR==NR {a[$1]; next} {if (FNR in a) print}' <(shuf -i 1-10 -n2) a
2 i am
9 sample

$ awk 'FNR==NR {a[$1]; next} {if (FNR in a) print}' <(shuf -i 1-10 -n2) a
4 do
6 and

改进

正如plundra 建议的in comments

shuf -n $(( $(wc -l < $FILENAME) / 10 )) $FILENAME

【讨论】:

  • 这个 b 是从哪里来的?我以前从未见过“NR in b”这种语法……你能解释一下 b[a[i]]=a[i]} NR in b 是什么意思吗?
  • 查看我的更新答案和一些解释。我希望它很清楚,不要犹豫,要求更多澄清。我认为这种方式非常快速和简单。
  • btw /bin/awk: 我的文件中会出现参数列表太长。
  • Havent' 想过这个问题。让我们换一种方式来做:给shuf 列表,因为它是一个文件。您可以在我更新的答案中找到它。
  • 为什么不只是shuf -n $(( $(wc -l &lt; $FILENAME) / 10 )) $FILENAME
【解决方案2】:

我认为这是最好的方法:

file=your file here
lines_in_file=`wc -l < $file`
lines_wanted=$(($lines_in_file/10))

shuf -n $lines_wanted $file

另一种创意解决方案:

echo $RANDOM 生成一个 0 到 32767 之间的随机数

那么,你可以这样做:

echo $(($RANDOM*100000/32767+1))

.. 获取 1 到 100000 之间的随机数(正如 nwellnhof 在下面的 cmets 中指出的那样,它不是 1 到 100000 之间的 任何 数字,而是 1 到 100000 之间的 32768 个可能数字之一100000,所以这是一种投影......)

所以:

file=your file here
lines_in_file=`wc -l $file | awk {'print $1'}`
lines_wanted=$(($lines_in_file/10))
for i in `seq 1 $lines_wanted`
 do line_chosen=$(($RANDOM*${lines_in_file}/32767+1))
sed "${line_chosen}q;d" $file
done

【讨论】:

  • 这是一个优雅而直接的解决方案,但您确定使用sed 对文件进行100k 次传递比让sort 读取完整文件一次并改组它更快吗?跨度>
  • 我确定,因为我刚刚尝试过并且确实很快。并且对排序做了同样的事情,并且花费了太长时间,我只是 CTRL+C'd 它。理想情况下是要求 sed 一次提取所有选定的行。但我不知道怎么做。另外,我喜欢我的解决方案,因为您从第一秒开始就看到了台词,并且您可以随时打断。
  • $RANDOM*100000/32767+1 只能产生 32768 个介于 1 和 100000 之间的数字,而不是整个范围。
  • 你说得对……我不能再挤$RANDOM了!
  • 刚刚发现 shuf 为我做了这件事!相应地编辑了我的答案,尽管我没有删除以前的方法,因为我认为作为练习练习很有用。
【解决方案3】:

我有这个脚本可以给你大概1/x 的行数。

#!/usr/bin/perl -w

use strict;

my $ratio = shift;

while (<>) {
    print if ((rand) <= 1 / $ratio);
}

对于足够大的$ratio,假设rand 的输出均匀分布。

假设你调用它random_select_ratio.pl,这样运行它可以获得 10% 的行:

random_select_ratio.pl 10 my_file

cat my_file | random_select_ratio.pl 10

【讨论】:

  • 谢谢。这就是我想在 python 中做的事情。但是 python readline 会将 \r 视为换行符。我不熟悉 perl,所以让我检查一下当 \r 字符包含在一行中时它是否可以工作。
【解决方案4】:

只需将文件作为输入运行这个 awk 脚本。

BEGIN { srand() }{ if (rand() < 0.10) print $0; }

我使用 awk 已经有一段时间了,但我相信应该这样做。

事实上,它确实按预期工作。大约 10% 的行被输出。在我使用 GNU awk 的 Windows 机器上,我运行:

awk "BEGIN { srand() }{ if (rand() < 0.10) print $0; }" <numbers.txt >nums.txt

numbers.txt 包含从 1 到 1,000,000 的数字,每行一个。经过多次运行,文件 nums.txt 通常包含大约 100,200 个项目,占 10.02%。

如果 awk 认为一行有问题,您可以随时更改记录分隔符。那是RS = "\n"; 但这应该是Linux机器上的默认值。

【讨论】:

    【解决方案5】:

    这是在编辑想法1.的一种方法:

    while readarray -n10 a; do
        [ ${#a[@]} = 0 ] && break
        printf "%s" "${a[${RANDOM: -1:1}]}"
    done < largefile.txt
    

    有点慢,虽然它比我机器上的 sort -R 方法快了大约 2.5 倍。

    我们使用readarray 一次从输入流中读取 10 行到一个数组中。然后我们使用$RANDOM 的最后一位数字作为该数组的索引并打印结果行。

    使用readarray/printf 组合应确保\r 字符未经修改地通过,就像在编辑的要求中一样。

    【讨论】:

      猜你喜欢
      • 2015-02-10
      • 2015-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-26
      • 2012-06-04
      相关资源
      最近更新 更多