Como posso extrair apenas linhas de um arquivo se a coluna 1 ocorrer pelo menos n vezes?

3

existe uma maneira de exibir as linhas contendo strings específicas (como reverente ::: N no exemplo abaixo) que não ocorrem por N vezes em um arquivo específico?

Eu sei que posso contar a ocorrência de alguma string com grep , gravá-la em um novo arquivo e depois contar as linhas com wc -l . No entanto, eu tenho um arquivo enorme onde eu preciso filtrar as linhas onde a seqüência de caracteres na coluna 1 não aparece 5 vezes na coluna 1 do arquivo.

Sugestões são muito apreciadas.

edit As linhas no arquivo_1.txt são classificadas alfabeticamente pela primeira coluna. O arquivo contém cerca de 1.000.000 de linhas, eu também poderia dividi-lo em alguns arquivos pequenos, é claro. A ordem da saída não importa.

arquivo_1.txt

realism:::N     1.33    depth:::N               3.11    341
realism:::N     1.33    problem:::N             2.68    335
realism:::N     1.33    now:::ADV               1.48    335
realism:::N     1.33    life:::N                2.69    334
renowned:::ADJ  1.41    be:::V                  1.85    15760
renowned:::ADJ  1.41    internationally:::ADV   2.23    9134
renowned:::ADJ  1.41    world:::N               4.36    6736
renowned:::ADJ  1.41    most:::ADV              2.38    5482
reverent:::ADJ  1.5     use:::V                 2.78    25
reverent:::ADJ  1.5     sacred:::ADJ            1.77    25
reverent:::ADJ  1.5     music:::N               4.31    25
reverent:::ADJ  1.5     devout:::ADJ            2.46    25
reverent:::ADJ  1.5     devotion:::N            2.36    25

output_file.txt

reverent:::ADJ  1.5 use:::V         2.78    25
reverent:::ADJ  1.5 sacred:::ADJ    1.77    25
reverent:::ADJ  1.5 music:::N       4.31    25
reverent:::ADJ  1.5 devout:::ADJ    2.46    25
reverent:::ADJ  1.5 devotion:::N    2.36    25
    
por dani_anyman 18.02.2017 / 17:21

2 respostas

2

awk com ajuda da substituição de processo ( <() ), sort e uniq :

awk 'NR==FNR{a[$0]; next} {for (i in a) if ($1==i) {print}}' \
      <(awk '{print $1}' file.txt | sort| uniq -c | awk '$1 >= 5 {print $2}') file.txt
  • awk '{print $1}' file.txt | sort| uniq -c | awk '$1 >= 5 {print $2}' obtém os primeiros campos em que o conteúdo ocorre maior ou igual a 5 vezes. A substituição do comando, <() , substitui o STDOUT por um descritor de arquivo, que é passado como o primeiro argumento para o principal worker awk process, com o arquivo de entrada original como segundo

  • NR==FNR{a[$0]; next} cria a matriz a com os elementos do primeiro arquivo ( NR==FNR ) como as chaves

  • {for (i in a) if ($1==i) {print}}' imprime as linhas de file.txt que têm as chaves da matriz a como o primeiro campo

A ressalva óbvia é que essa abordagem está lendo o arquivo duas vezes, o que pode não ser a solução ideal para um arquivo de entrada grande ( na definição usual ), em que alta velocidade e / ou baixo recurso o uso é esperado.

Exemplo:

% cat file.txt                                                                                                     
realism:::N     1.33    depth:::N               3.11    341
realism:::N     1.33    problem:::N             2.68    335
realism:::N     1.33    now:::ADV               1.48    335
realism:::N     1.33    life:::N                2.69    334
renowned:::ADJ  1.41    be:::V                  1.85    15760
renowned:::ADJ  1.41    internationally:::ADV   2.23    9134
renowned:::ADJ  1.41    world:::N               4.36    6736
renowned:::ADJ  1.41    most:::ADV              2.38    5482
reverent:::ADJ  1.5     use:::V                 2.78    25
reverent:::ADJ  1.5     sacred:::ADJ            1.77    25
reverent:::ADJ  1.5     music:::N               4.31    25
reverent:::ADJ  1.5     devout:::ADJ            2.46    25
reverent:::ADJ  1.5     devotion:::N            2.36    25

% awk 'NR==FNR{a[$0]; next} {for (i in a) if ($1==i) {print}}' <(awk '{print $1}' file.txt | sort| uniq -c | awk '$1 >= 5 {print $2}') file.txt
reverent:::ADJ  1.5     use:::V                 2.78    25
reverent:::ADJ  1.5     sacred:::ADJ            1.77    25
reverent:::ADJ  1.5     music:::N               4.31    25
reverent:::ADJ  1.5     devout:::ADJ            2.46    25
reverent:::ADJ  1.5     devotion:::N            2.36    25
    
por heemayl 18.02.2017 / 19:11
2

Supondo que as linhas estão classificadas (agrupadas)

Desde que você mencionou as linhas são classificadas alfabeticamente pela primeira coluna : O script abaixo lerá as linhas, mantendo-as em buffer, desde que a linha inicie com a mesma string na coluna um como a anterior .

Se não , o buffer é adicionado a um arquivo de saída (somente) se o número de strings atender a um determinado valor , o buffer é subseqüentemente limpo e o processo é iniciado novamente, até que o arquivo esteja pronto.

Isso deve ser muito rápido em arquivos grandes, embora eu não tenha executado um teste de tempo (ainda) em arquivos grandes

O script

#!/usr/bin/env python3
import sys

#-- set the minimum number below
n = 5
# don't change anything below
f = sys.argv[1]; out = sys.argv[2]; mark1 = ""; lines = []

def write_out(lines):
    if len(lines) >= n:
        with open(out, "a+") as wrt:
            for line in lines:
                wrt.write(line)

with open(f) as read:
    for l in read:
        mark2 = l.split()[0]
        if mark2 == mark1:
            lines.append(l)
        else:
            write_out(lines)
            lines = [l]
        mark1 = mark2
# add the last set of lines
write_out(lines)

Para usar

  1. Copie o script em um arquivo vazio, salve-o como get_lines.py
  2. Na cabeça do script, defina o número mínimo de linhas, começando com a string.
  3. Execute-o com o arquivo de entrada e saída como argumentos:

    python3 /path/to/get_lines.py <input_file> <output_file>
    

Saída (como esperado) em output_file , se definirmos o limite como 5:

reverent:::ADJ  1.5     use:::V                 2.78    25
reverent:::ADJ  1.5     sacred:::ADJ            1.77    25
reverent:::ADJ  1.5     music:::N               4.31    25
reverent:::ADJ  1.5     devout:::ADJ            2.46    25
reverent:::ADJ  1.5     devotion:::N            2.36    25
    
por Jacob Vlijm 18.02.2017 / 19:15