Reformatar tabelas

3

Eu tenho algumas tabelas ( table.txt ) que foram mal construídas e apresentam redundância nos resultados, como segue:

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...

Em vez disso, gostaria de ter:

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...

Então o problema é que os resultados são apresentados duas vezes na tabela. Isso significa (com o exemplo fornecido) que após o '1971' eu deveria esperar ano '1972' e não '1971' novamente. Existe uma maneira de excluir os resultados redundantes usando sh / bash?

Tenho de notar que os meus dados correm ao longo de 1971 até 2099, todos os dias, e que têm exactamente o mesmo formato, mesmo após o ano 2000, da seguinte forma:

YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
2000 1     1   875
2000 1     2   456
...
2099 12    31  321
    
por steve 16.10.2015 / 15:00

3 respostas

3

Aqui estão dois sed loops mutuamente exclusivos:

sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' <<""
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999
1972 1     1   933
1972 1     2   837
...
1972 12    31  343
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
...
1971 12    31  685
1972 1     1   549
1972 1     2   746
...
1972 12    31  999

Basicamente sed tem dois estados - p rint e eat . No primeiro estado - o estado p rint - sed automaticamente p rints a cada linha de entrada, em seguida, verifica o padrão / 12 * 31 / . Se o espaço de padrão atual fizer ! não corresponder, ele será d eleted e sed obterá a próxima linha de entrada e iniciará o script novamente a partir do topo - no comando p rint, sem tentar executar nada a seguir o comando d elete de todo.

Quando uma linha de entrada faz corresponder a / 12 * 31 / , no entanto, sed passa para a segunda metade do script - o loop eat . Primeiro, define uma ramificação : label denominada n ; em seguida, sobrescreve o espaço de padrão atual com a linha de entrada n ext e, em seguida, compara o espaço de padrão atual com o padrão correspondido // . Como a linha que correspondia a ela antes foi substituída pela n ext, a primeira iteração desse loop eat não corresponde e toda vez que ocorre ! not sed b classifica novamente para o rótulo :n para obter a linha de entrada n ext e mais uma vez para compará-la ao padrão // da última correspondência.

Quando outra correspondência é finalmente feita - algumas linhas den ext de 365% mais tarde - sed faz -n ot imprimi-la automaticamente quando completa seu script, puxa a próxima linha de entrada e inicia novamente a partir do topo em o comando p rint em seu primeiro estado. Assim, cada estado de loop passará para o próximo na mesma chave e, no meio tempo, fará o mínimo possível para encontrar a próxima chave.

Observe que o script inteiro é concluído sem invocar uma única rotina de edição e que é necessário apenas compilar o regexp único. O autômato resultante é muito simples - ele entende apenas [123 ] e [^123 ] . Além disso, pelo menos metade das comparações provavelmente serão feitas sem nenhuma compilação, porque o único endereço referenciado no loop eat é o // empty. Por isso, sed pode concluir esse loop inteiramente com uma única chamada regexec() por linha de entrada. sed may faz similar para o loop p rint também.

cronometrado

Eu estava curioso sobre como as várias respostas aqui podem ser executadas e, assim, criei minha própria tabela:

dash <<""
    d=0 D=31 IFS=: set 1970 1
    while   case  "$*:${d#$D}" in (*[!:]) ;;
            ($(($1^($1%4)|(d=0))):1:)
                     D=29 set $1 2;;
            (*:1:)   D=28 set $1 2;;
            (*[3580]:)
                     D=30 set $1 $(($2+1));;
            (*:)     D=31 set $(($1+!(t<730||(t=0)))) $(($2%12+1))
            esac
    do      printf  '%-6d%-4d%-4d%d\n' "$@" $((d+=1)) $((t+=1))
    done|   head    -n1000054 >/tmp/dates
dash <<<''  6.62s user 6.95s system 166% cpu 8.156 total

Isso coloca um milhão de linhas + em /tmp/dates e dobra a saída para cada um dos anos 1970 - 3338. O arquivo se parece com:

tail -n1465 </tmp/dates | head; echo; tail </tmp/dates
3336  12  27  728
3336  12  28  729
3336  12  29  730
3336  12  30  731
3336  12  31  732
3337  1   1   1
3337  1   2   2
3337  1   3   3
3337  1   4   4
3337  1   5   5

3338  12  22  721
3338  12  23  722
3338  12  24  723
3338  12  25  724
3338  12  26  725
3338  12  27  726
3338  12  28  727
3338  12  29  728
3338  12  30  729
3338  12  31  730

... algumas delas de qualquer maneira.

E então tentei os diferentes comandos:

for  cmd in "sort -uVk1,3" \
            "sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn'" \
            "awk '"'{u=$1 $2 $3 $4;if (!a[u]++) print;}'\'
do   eval   "time ($cmd|wc -l)" </tmp/dates
done
500027
( sort -uVk1,3 | wc -l; ) \
1.85s user 0.11s system 280% cpu 0.698 total

500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.64s user 0.09s system 110% cpu 0.659 total

500027
( awk '{u=$1 $2 $3 $4;if (!a[u]++) print;}' | wc -l; ) \
1.46s user 0.15s system 104% cpu 1.536 total

Os comandos sort e sed foram concluídos em menos da metade do tempo awk - e esses resultados foram típicos. Eu corri várias vezes. Parece que todos os comandos estão escrevendo o número correto de linhas também - e então provavelmente todos trabalham.

sort e sed estavam razoavelmente bem no pescoço e no pescoço - com sed geralmente à frente - para o tempo de conclusão de cada execução, mas sort faz mais trabalho real para alcançar seus resultados do que qualquer um dos outros dois comandos. Ele está executando trabalhos paralelos para concluir sua tarefa e beneficia muito da minha CPU multi-core. awk e sed atrelam o único núcleo atribuído a eles durante todo o tempo que processam.

Os resultados aqui são de um padrão% GNU sed , mas tentei outro. Na verdade, tentei todos os três comandos com outros binários, mas apenas o comando sed realmente funcionou com as minhas ferramentas de herança. Os outros, como eu suponho devido à sintaxe não padrão, simplesmente desistem com erro antes de decolarem.

É bom usar a sintaxe padrão quando possível - você pode usar livremente implementações mais simples, aprimoradas e eficientes em muitos casos dessa maneira:

PATH=/usr/heirloom/bin/posix2001:$PATH; time ...
500027
( sed -ne'p;/ 12 * 31 /!d;:n' -e'n;//!bn' | wc -l; ) \
0.31s user 0.12s system 136% cpu 0.318 total
    
por 16.10.2015 / 17:16
3
$ (head -1 table.txt ; tail -n +2 table.txt | sort -u -V -k1,3)
YEAR MONTH DAY RES
1971 1     1   245
1971 1     2   587
1971 2     1   587
1971 12    31  685
1972 1     1   549
1972 1     2   746
2000 1     1   875
2000 1     2   456
2099 12    31  321
    
por 16.10.2015 / 15:11
3

tente canalizar para o awk

awk '!a[$0]++' files.txt > new_files.txt
mv new_files.txt files.txt

esta linha de saída apenas uma vez.

edit: (não tenho certeza se concatenar var fará o truque)

awk '{u=$1 $2 $3 $4 ; if ( !a[u]++ ) print ; } ' ...
    
por 16.10.2015 / 15:06