Saída diagonal de um arquivo

6

Como você produziria a diagonal de um arquivo?

por exemplo, eu tenho um arquivo com o seguinte dentro.

1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5

A saída deveria se parecer com: 1 7 3 9 5 ou algo assim.

Eu posso produzir uma coluna por meio do cut (cut -d "," -f5 filename), mas não tenho certeza do que escrever para produzir apenas a diagonal.

    
por Superstar 04.07.2017 / 23:27

7 respostas

7

solução awk, não tão elegante quanto a solução @don_chrissti, mas funciona onde não é um quadrado.

awk -F, '{a=a$++n" "}END{print a}' file
    
por 04.07.2017 / 23:41
5

Python e numpy

Os dados de entrada que estamos vendo podem ser tratados como matriz ou matriz bidimensional. Agora, se abordarmos o problema desse ponto de vista, há várias ferramentas computacionais que podem ser usadas para manipular matrizes. Em particular, o módulo numpy do Python permite isso. Assim, poderíamos usar duas coisas - a função loadtxt() e diagonal() para extrair os dados desejados:

$ python -c 'import sys,numpy;a=numpy.loadtxt(sys.argv[1],dtype=int,delimiter=",");print( a.diagonal() )'  input.txt                                                                    
[1 7 3 9 5]

Agora, esta é a grande parte do trabalho realizado. Para tornar a saída bonita, basta converter os dados obtidos em cadeias de caracteres e criar uma cadeia separada por espaços entre as cadeias individuais. Assim:

$ python -c 'import sys,numpy;a=numpy.loadtxt(sys.argv[1],delimiter=",");print(" ".join( [ str(int(i)) for i in a.diagonal() ]))'  input.txt                                            
1 7 3 9 5

Claro, tudo isso não precisa ser feito como uma linha. Por uma questão de legibilidade, podemos criar um pequeno script, que também nos permitirá processar todos os nomes de arquivos dados como argumentos na linha de comando:

#!/usr/bin/env python
import sys
import numpy as np

for filename in sys.argv[1:]:
    data=np.loadtxt(filename,delimiter=",")
    diag = data.diagonal()
    result_string = " ".join( [ str(int(i)) for i in diag ] ) 
    print(result_string)
    
por 05.07.2017 / 06:40
3
sed -ne '
   y/,/\n/;G;s/\n$//

   y/\n_/_\n/;:loop
      /_$/{
         s///
         s/^[^_]*_//
         bloop
      }
   y/\n_/_\n/;P

   s/.*//;H
' input.file | paste -sd' '

Mantemos um razão nos campos separados por vírgulas para pular no espaço padrão no espaço de espera.

O looping corta o espaço do padrão de ambas as extremidades para chegar à situação quando o lado mais à esquerda está pronto para ser impresso. Podemos imaginar que esteja queimando uma vela de ambas as extremidades (no entanto, a taxa de queima é diferente). A partir da frente, cortamos um campo separado por vírgula, enquanto, do final, soltamos um \n à direita. A queima continua até que não haja mais linhas novas.

E agora o elemento diagonal está na frente do espaço do padrão.

O artefato y/\n_/_\n/ é para contornar o fato de que POSIX sed não tem a nova linha negada dentro de uma classe de caracteres, [^\n] .

Como último passo para a linha atual, a área de espera é incrementada. O comando paste é para obter a saída em uma única linha.

Se todos os campos forem numéricos no seu csv, você também poderá usar o seguinte snippet de dc . O tr é remover as vírgulas, pois os campos dc são delimitados por espaço e números negativos começam com _ em vez de -

tr ',-' ' _' < file | dc -e '[q]sq [s0zlk<a]sa [?z0=qzlk1+dsk<an32ancz0=?]s? 0skl?x[]p'

Definimos 3 macros, q para desistir quando terminar, a para um looping para excluir os elementos do final (popping) e ? para configurar um loop para executar uma leitura orientada por linha e invocando a macro a e, em seguida, imprimindo o elemento de diagnóstico agora exposto.

tr ... |
dc -e '
   # quit macro
   [q]sq

   # macro to pop elements from stack till they are more than counter k
   [s0 zlk<a]sa

   # do-while loop for reading lines
   [
      ?z0=q       # quit if line read is empty
      zlk1+dsk<a  # increment the counter k and compare it against NF (z) call a if >
      n 32an      # print the diagonal element and a space (no newlines)
      c z0=?      # invoke myself again 
   ]s?

   # main
   0sk  # initialize the counter k
   l?x  # call the macro ? to start the file read-in loop
   []p  # print a trailing newline
'

Saída:

1 7 3 9 5
    
por 05.07.2017 / 10:02
3
  1. POSIX shell:

    n=0
    while IFS=',' && read x ; do
        set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
    done < inputfile
    
  2. bash pode ser mais conciso:

     n=0; while IFS=',' read -a x ; do echo "${x[((n++))]}" ; done < inputfile
    

Observe que é fácil obter diagonais para todas as quatro rotações dos dados, filtrando para tac e rev , ou tac sozinho - se os dados forem um quadrado < em> .csv array). Exemplos de shell POSIX , mas primeiro faça alguns novos valores de entrada assimétricos:

seq 25 | paste -d, - - - - - | tee asym_input

Saída:

1,2,3,4,5
6,7,8,9,10
11,12,13,14,15
16,17,18,19,20
21,22,23,24,25
  1. A \ da esquerda para a direita na diagonal , (a questão OP , com entrada diferente):

    n=0
    while IFS=',' && read x ; do
         set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
    done < asym_input
    

    Saída:

    1
    7
    13
    19
    25
    
  2. Uma / da esquerda para a direita na diagonal :

    n=0
    tac asym_input | while IFS=',' && read x ; do
         set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
    done
    

    Saída:

    21
    17
    13
    9
    5
    
  3. Uma / da direita para a esquerda na diagonal :

    n=0
    rev asym_input | while IFS=',' && read x ; do
         set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
    done | rev
    

    Saída:

    5
    9
    13
    17
    21
    
  4. Uma \ da direita para a esquerda na diagonal :

    n=0
    tac asym_input | rev | while IFS=',' && read x ; do
         set -- $x ; shift $n ; echo "$1" ; n=$((n+1))
    done | rev
    

    Saída:

    25
    19
    13
    7
    1
    
por 05.07.2017 / 05:53
2

Resumo:

  1. Quadrado .....: awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file
  2. Retangular:

    awk -F, ' NR==1{printf($1);next}
              {printf(" "$(NR>NF?NF:NR))}END{printf("\n")}
            ' file'
    
  3. Outras diagonais:

    awk -F, -vdiag=9 -vdir=-1 '
        {d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
        {printf("%s%s",NR==1?"":" ",$d)}
        END {printf("\n")}
     ' file
    
  4. Posix que seleciona o número diagonal e a direção / vs \ . (o código é longo, por favor leia no final deste post).

Detalhes

Matriz quadrada

Com o awk, a solução mais elegante é:

$  awk -F, '{print $NR}' file
1
7
3
9
5

Para ter uma saída de uma linha, você poderia fazer (com um espaço à direita):

$ awk -F, -v ORS=" " '{print $NR}' file; echo
1 7 3 9 5 

Se você precisa ter a saída sem espaços à direita:

$  awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("\n")}' file
1 7 3 9 5

Retangular

Para um arquivo com, por exemplo, isto:

$ cat file
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
6,7,8,9,0
1,2,3,4,5
a,b,c,d,e
f,g,h,i,j
k,l,m,n,o
p,q,r,s,t
u,v,w,x,y

A solução acima irá imprimir espaços em branco:

$ awk -F, '{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' file
1 7 3 9 5     |

Se o que você deseja, neste caso, é parar o processamento, verificar se o número do registro é maior que o número de campos pode ser uma solução (se o número de campos for alterado para cada linha, isso pode não ser o correto solução):

$ awk -F, 'NR>NF{exit}{printf(NR==1?$NR:" "$NR)}END{printf("|\n")}' infile
1 7 3 9 5|

Se o que você deseja é imprimir o último campo em qualquer linha onde NR > NF:

$ awk -F, 'NR==1{printf($1);next}{printf(" "$(NR>NF?NF:NR))}END{printf("|\n")}' file
1 7 3 9 5 e j o t y|

Outras diagonais

Se o que é necessário é uma diagonal diferente da "diagonal principal", podemos sinalizar isso configurando a variável diag com um valor diferente de 0 (0 é a diagonal principal neste código):

$ awk -F, -vdiag=3   '   {d=NR+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
                         {printf("%s%s",NR==1?"":" ",$d)}
                         END {printf("\n")}
                     ' file
4 0 5 0 5 e j o t y

Observe que o valor de diag pode ser negativo:

 $ awk -F, -vdiag=-3 '   {d=NR+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
                         {printf("%s%s",NR==1?"":" ",$d)}
                         END {printf("\n")}
                     ' infile
 1 6 1 6 2 c i o t y

E a diagonal pode ser igual a / em vez de \ com mais matemática:

$ awk -F, -vdiag=4 -vdir=-1 '
    {d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
    {printf("%s%s",NR==1?"":" ",$d)}
    END {printf("\n")}
' file
5 9 3 7 1 a f k p u

$ awk -F, -vdiag=9 -vdir=-1 '
    {d=(NR-1)*(dir>0?1:-1)+1+diag;d=(d<1?1:d);d=(d>NF?NF:d)}
    {printf("%s%s",NR==1?"":" ",$d)}
    END {printf("\n")}
' infile
5 0 5 0 5 e i m q u

Posix shell

Com um arquivo de entrada diferente:

$ printf '%s\n' {1..6}{1..5} 7{1..3} | pr -ta -5 -s',' | tee inputfile
11,12,13,14,15
21,22,23,24,25
31,32,33,34,35
41,42,43,44,45
51,52,53,54,55
61,62,63,64,65
71,72,73

Um equivalente ao awk no shell compatível com Posix pode ser:

diagPosix(){ diag=${1%%[!0-9+-]*} dir=$(((${2:-1}>0)?1:-1)) n=0 a=""
             while read x ; do
#                echo "testing $n $x"
                 IFS=',' eval 'set -- $x'  # Place values in pos parms.
                 a=$(( diag + n*dir    ))  # calculate position a
                 b=$(( (a<0)?0:a       ))  # low limit is zero (0)
                 d=$(( (b>$#-1)?$#-1:b ))  # upper limit is ($#-1)
#                echo "a=$a b=$b d=$d #=$# n=$n"
                 shift $d                  # remove leading parms
                 printf '%s' "$s" "$1"     # print parm (and an space)
                 s=" "                     # Next loop will have space.
                 n=$((n+1))                # In which line are we?
             done <"${3:-inputfile}"
             echo 
           }
diagPosix "$@"

que, com a entrada acima, funcionará da seguinte forma:

$ ./script 0 1 inputfile
11 22 33 44 55 65 73

$ ./script -2 1 inputfile
11 21 31 42 53 64 73

$ ./script 4 -1 inputfile
15 24 33 42 51 61 71

O código foi testado em alguns shells e funciona bem.

ash             : 11 22 33 44 55 65 73
/usr/bin/yash   : 11 22 33 44 55 65 73
y2sh            : 11 22 33 44 55 65 73
dash            : 11 22 33 44 55 65 73
zsh/sh          : 11 22 33 44 55 65 73
b203sh          : 11 22 33 44 55 65 73
b204sh          : 11 22 33 44 55 65 73
b205sh          : 11 22 33 44 55 65 73
b30sh           : 11 22 33 44 55 65 73
b32sh           : 11 22 33 44 55 65 73
b41sh           : 11 22 33 44 55 65 73
b42sh           : 11 22 33 44 55 65 73
b43sh           : 11 22 33 44 55 65 73
b44sh           : 11 22 33 44 55 65 73
lksh            : 11 22 33 44 55 65 73
mksh            : 11 22 33 44 55 65 73
ksh93           : 11 22 33 44 55 65 73
attsh           : 11 22 33 44 55 65 73
zsh/ksh         : 11 22 33 44 55 65 73

Ele falha para zsh (não em emulação) porque zsh não é dividido por padrão e porque a numeração de array começa em 1 (não 0). Foi testado em csh e tcsh mas não funciona.
E é não esperado que funcione lá (não use csh para scripts!).

As soluções que funcionam de baixo para cima devem ser fáceis de construir usando o tac na entrada.

    
por 05.07.2017 / 22:57
1

Aqui está outra alternativa usando o awk:

awk -F, '{printf "%s%s",$NR,NR==NF?ORS:OFS}' file 
1 7 3 9 5
    
por 05.07.2017 / 12:11
1

Uma solução baseada em shell / corte:

index=1
while read line
do
  cut -d, -f ${index} <<< "$line"
  index=$((++index))
done < input
    
por 05.07.2017 / 15:34