从数据文件中随机绘制一定数量的线


13

我有一个数据列表,例如

12345
23456
67891
-20000
200
600
20
...

假设此数据集的大小(即文件行)为N。我想m从此数据文件中随机绘制线条。因此,输出应该是两个文件,一个是包含这些m数据行的文件,另一个是包含数据行的文件N-m

有没有办法使用Linux命令来做到这一点?


1
您是否担心线的顺序?例如。您是否要保持源顺序,还是希望该序列本身是随机的,以及选择的行是随机的?
Peter.O 2012年

Answers:


18

这可能不是最有效的方法,但它可以起作用:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

具有$m包含行数。


@userunknown,sort -R负责随机性。不知道您是否为此答对了票,但请先在手册页中查找。
罗伯·沃特斯

2
请注意,sort -R它不会完全随机地对输入进行排序:它会将相同的行分组。因此,如果输入的是例如foofoobarbar且m = 2,则一个文件将包含fooS和其他将同时包含bar第 GNU coreutils还具有shuf,它使输入行随机化。另外,您不需要临时文件
吉尔(Gilles)'所以

为什么不shuf <file> |head -n $m呢?
emanuele 2014年

@emanuele:因为我们需要头部和尾部都放在两个单独的文件中。
罗伯·沃特斯

5

此bash / awk脚本随机选择行,并在两个输出文件中保持原始顺序。

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

根据问题中的数据输出。

12345
23456
200
600
========
67891
-20000
20

4

与Unix一样,该TM也有一个实用程序。

每日程序:split
split将以许多不同的方式分割文件,-b字节,-l行,-n输出文件数。我们将使用该-l选项。由于您要选择随机行,而不仅仅是第一行m,因此我们将sort首先随机选择文件。如果您想阅读sort,请在这里参考我的答案。

现在,实际的代码。这很简单,真的:

sort -R input_file | split -l $m output_prefix

这将创建两个文件,一个文件包含m行,一个文件包含N-m行,分别名为output_prefixaaoutput_prefixab。确保m是您想要的较大文件,否则您将获得多个长度为m1的文件(其中一个带有N % m)。

如果要确保使用正确的大小,请执行以下代码:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

编辑:引起我注意的是某些sort实现没有-R标记。如果有perl,可以替代perl -e 'use List::Util qw/shuffle/; print shuffle <>;'


1
不幸的是,sort -R似乎只存在于某些版本中(可能是gnu版本)。对于其他平台,我编写了一个名为“ randline”的工具,该工具只对stdin进行随机化处理。任何需要的人都可以在beesbuzz.biz/code上找到。(我倾向于将文件内容改组很多。)
蓬松的2012年

1
请注意,sort -R它不会完全随机地对输入进行排序:它会将相同的行分组。因此,如果输入的是例如foofoobarbar且m = 2,则一个文件将包含fooS和其他将同时包含bar第 GNU coreutils还具有shuf,它使输入行随机化。另外,您可以使用headtail代替选择输出文件名split
吉尔(Gilles)'所以

4

如果您不介意对行进行重新排序,并且拥有GNU coreutils(即,在非嵌入式Linux或Cygwin上,自shuf6.0版以来就不太古老),shuf(“随机播放”)将对文件的行进行随机重新排序。因此,您可以将文件随机播放,并将前m行分配到一个文件中,而将其余m行分配到另一个文件中。

没有理想的方式来执行该调度。您不能只是连锁headtail因为head会向前缓冲。您可以使用split,但是在输出文件名方面没有任何灵活性。您可以使用awk,当然:

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

您可以使用sed,它比较晦涩,但对于大文件来说可能更快。

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

或者tee,如果您的平台具有,则可以使用来复制数据/dev/fd。如果m很小就可以:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

可移植的是,您可以使用awk依次分派每行。请注意,awk在初始化其随机数生成器方面不是很好。随机性不仅绝对不适合密码学,而且对于数值模拟也不是很好。在一秒钟的时间内,任何系统上所有awk调用的种子都相同。

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

如果需要更好的随机性,则可以在Perl中做同样的事情,这会很好地植入RNG。

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = `wc -l <input`;
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42

@Gilles:对于awk例如: -v N=$(wc -l <file) -v m=4...这只是打印“随机”线的时候,随机值是小于$m,而不是打印$m随机线......如此看来,perl可能是用做同样的事情兰特,但我不perl不太了解编译错误:-e第7行的语法错误,靠近“)print”
Peter.O,2012年

@ Peter.O谢谢,这就是在浏览器中键入和粗心地编辑所产生的。我已经修复了awk和perl代码。
吉尔(Gilles)'所以

这3种方法都运作良好且快速..谢谢(+1)...我正在慢慢了解perl ...这是shuf示例中一个特别有趣且有用的文件拆分方法。
Peter.O 2012年

缓冲问题?。我想念什么吗?的head cat组合导致在随后的第二检测数据的丢失3-4 .... TEST 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. TEST 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... wc -l为的结果输出TEST 1-25000 5000(良好),但对于试验3-45000 4539(不好)。。差异取决于所涉及的文件大小...这是我的测试代码
Peter.O 2012年

@ Peter.O再一次,谢谢。确实,请继续head阅读;它前面读取的内容和未打印的内容将被丢弃。我已经用不太优雅但(我可以肯定)正确的解决方案更新了答案。
吉尔(Gilles)'所以

2

假设m = 7N = 21

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

注意:如果您用7诸如$1或的变量替换$m,则必须使用seq,而不是{from..to}-notation,它不会进行变量扩展。

它的工作原理是从文件中逐行删除,这会越来越短,因此可以删除的行号必须越来越小。

不应将它用于较长的文件和许多行,因为对于每个数字,平均而言,对于第一个文件,需要读取一半的文件,对于第二个sed代码,则需要读取整个文件。


他还需要一个带有删除行的文件。
罗伯·沃特斯

我认为“包括这m行数据”也应该意味着including them原始行,因此including,不是consisting of,并且不使用only,但是我想您的解释是user288609的含义。我将相应地调整脚本。
用户未知

看起来不错。``
罗布·沃特斯

@user未知:您+1在错误的位置。它应该是rnd=$((RANDOM%(N-i)+1))其中N =在你的榜样21 ..这导致目前sed崩溃时rnd被评估为0。..而且,随着所有文件重写,它的伸缩性也不太好。例如123秒来提取10,000行的文件5000条随机线 0.03秒有更直接的方法...
Peter.O

@ Peter.O:您是正确的(更正),您是正确的。
用户未知
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.