【问题标题】:How to randomly remove 100 blocks from a text file如何从文本文件中随机删除 100 个块
【发布时间】:2018-11-08 21:32:29
【问题描述】:

假设我有一个巨大的文本文件,如下所示:

19990231  
blabla   
sssssssssssss  
hhhhhhhhhhhhhh  
ggggggggggggggg                  

20090812  
blbclg  
hhhhhhhhhhhhhh  
ggggggggggggggg  
hhhhhhhhhhhhhhh

20010221  
fgghgg  
sssssssssssss  
hhhhhhhhhhhhhhh  
ggggggggggggggg

<etc>  

如何随机删除 100 个以数字字符开头并以空行结尾的块?例如:

20090812  
blbclg  
hhhhhhhhhhhhhh  
ggggggggggggggg  
hhhhhhhhhhhhhhh  
<blank line>

【问题讨论】:

  • 欢迎来到 SO,您的问题不清楚,请将您的示例输入和示例输出包装到代码标签 {} 按钮中,然后告诉我们。
  • 您不能从文件中删除材料;您必须将其读入,并将选定的部分写回新文件。首先编写一个简单复制的程序;然后添加跳过某些部分的逻辑。
  • 为什么你有pythonawk这两个标签?
  • 好的。如果您可以将整个文件读入内存,这很容易做到。但是,如果您不想这样做,则需要对文件进行两次传递。在第一次通过时,您计算块,在第二次通过时,您将想要的块复制到一个新文件中。当然,如果您可以提供块数作为输入参数,则可以一次性完成。
  • @jww 再次对所有答案投了反对票,所以我再次对所有答案投了赞成票。

标签: python linux awk


【解决方案1】:

这并不难。诀窍是首先定义记录,这可以通过记录分隔符来完成:

RS:RS的字符串值的第一个字符为输入记录分隔符; &lt;newline&gt; 默认情况下。如果RS 包含多个字符,则未指定结果。 如果RS 为空,则记录由由&lt;newline&gt; 加上一个或多个空行组成的序列分隔,前导或尾随空行不应导致输入的开头或结尾处为空记录,并且无论FS 的值是什么,&lt;newline&gt; 始终是字段分隔符。

所以记录数由下式给出:

$ NR=$(awk 'BEGIN{RS=""}END{print NR}' <file>)

然后您可以使用shuf 获得1 到NR 之间的一百个随机数:

$ shuf -i 1-$NR -n 100

您在awk 中再次输入此命令以选择记录:

$  awk -v n=100 '(NR==n){RS="";ORS="\n\n"}       # reset the RS for reading <file>
                 (NR==FNR){print $1; a[$1];next} # load 100 numbers in memory
                 !(FNR in a) { print }           # print records
                ' <(shuf -i 1-$NR -n 100) <file>

我们也可以使用Knuth shuffle 一次性完成此操作,并对文件进行两次传递

awk -v n=100 '
   # Create n random numbers between 1 and m
   function shuffle(m,n,    b, i, j, t) {
       for (i = m; i > 0; i--) b[i] = i
       for (i = m; i > 1; i--) {
          # j = random integer from 1 to i
          j = int(i * rand()) + 1
          # swap b[i], b[j]
          t = b[i]; b[i] = b[j]; b[j] = t
       }
       for (i = n; i > 0; i--) a[b[i]]
   }
   BEGIN{RS=""; srand()}
   (NR==FNR) {next}
   (FNR==1)  {shuffle(NR-1,n) }
   !(FNR in a) { print }' <file> <file>

【讨论】:

  • 有人再次对所有答案投了反对票。我正在恢复它。
  • 不是“某人”,我们的常驻惯犯@jww。我再次对所有答案投了赞成票。
【解决方案2】:

使用awkshuf删除6个块中的4个块,每个块长3行:

$ cat tst.awk
BEGIN { RS=""; ORS="\n\n" }
NR==FNR { next }
FNR==1 {
    cmd = sprintf("shuf -i 1-%d -n %d", NR-FNR, numToDel)
    oRS=RS; RS="\n"
    while ( (cmd | getline line) > 0 ) {
        badNrs[line]
    }
    RS=oRS
    close(cmd)
}
!(FNR in badNrs)

$ awk -v numToDel=4 -f tst.awk file file
1
2
3

10
11
12

只需将numToDel=4 更改为numToDel=100 即可获得真正的输入。

用于测试上述的输入文件由以下人员生成:

$ seq 18 | awk '1; !(NR%3){print ""}' > file

生产的:

$ cat file
1
2
3

4
5
6

7
8
9

10
11
12

13
14
15

16
17
18

【讨论】:

    【解决方案3】:

    这里是一个没有洗牌的解决方案

    $ awk -v RS= -v ORS='\n\n' -v n=100 '
            BEGIN  {srand()} 
            NR==FNR{next} 
            FNR==1 {r[0]; 
                    while(length(r)<=n) r[int(rand()*NR)]} 
           !(FNR in r)' file{,} 
    

    双通算法,第一轮是统计记录数,创建一个随机的索引号列表,直到需要的值,打印不在列表中的记录。请注意,如果删除的数字更接近记录数,性能会下降(获得新数字的概率会很低)。对于您的情况,600 人中有 100 人不会有问题。在另一种情况下,选择要打印的记录而不是删除的记录会更容易。

    由于shuf 非常快,我认为这不会为您带来性能提升,但这样可能更简单。

    【讨论】:

    • @EdMorton 不,它会在length(r)==n+1 时停止。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-26
    • 2023-03-25
    • 2016-10-07
    • 2023-04-04
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多