Crear listas de palabras según números binarios.


12

Tengo una matriz que se parece a la siguiente:

Entrada :

A   B   C   D   E   F   G   H   I 
0   0   0   0   1   0   0   0   1
0   0   0   1   0   0   0   0   0  
0   0   0   1   0   0   0   0   0  
1   0   0   0   0   0   0   0   0  
1   0   1   0   0   0   1   0   0  
1   0   0   1   0   0   0   1   0  
1   0   0   0   1   1   1   0   0  

Y me gustaría extraer para cada fila la lista de letras correspondiente al valor 1.

Salida :

E,I 
D
D
A
A,C,G  
A,D,H  
A,E,F,G  

Intenté dividir el encabezado y unir las palabras con los números, pero fallé.

Respuestas:


12

En awk:

NR == 1 { for(column=1; column <= NF; column++) values[column]=$column; }
NR > 1 { output=""
        for(column=1; column <= NF; column++)
                if($column) output=output ? output "," values[column] : values[column]
        print output }

66
también puede usarNR == 1 { split($0,values) }
Sundeep

Eso es saltarse la segunda línea. Considere poner un nextal final de la primera línea para que no necesite probar una condición opuesta para las líneas posteriores.
Ed Morton

1
Parece que el texto de entrada original tenía una línea en blanco adicional, que codifiqué. Ha sido editado desde fuera, por lo que acaba de cambiar NR > 2a NR > 1.
Jeff Schaller

1
Gracias por el consejo "golf", Sundeep! Creo que prefiero el bucle 'for' explícito, ya que se alinea visual / lógicamente con el bucle 'for' en el cuerpo.
Jeff Schaller

1
@ fusion.slope, pase el código completo en un argumento entre comillas simples awko pegue el código en un archivo y ejecúteloawk -f that.script.file input-file
Jeff Schaller

6

Otro con perl

$ perl -lane 'if($. == 1){ @h=@F }
              else{@i = grep {$F[$_]==1} (0..$#F); print join ",",@h[@i]}
             ' ip.txt
E,I
D
D
A
A,C,G
A,D,H
A,E,F,G
  • -aopción para dividir la línea de entrada en espacios en blanco, disponible en @Fmatriz
  • if($. == 1){ @h=@F } guardar el encabezado si la primera línea
  • @i = grep {$F[$_]==1} (0..$#F) guardar índice si la entrada es 1
  • print join ",",@h[@i]imprima solo los índices de la matriz de encabezado utilizando ,como separador

4

Aún por diversión, una zshversión:

{
   read -A a  &&
   while read -A b; do
     echo ${(j<,>)${(s<>)${(j<>)a:^b}//(?0|1)}}
   done
} < file
  • ${a:^b} comprime las dos matrices, por lo que obtienes A 0 B 0 C 0 D 0 E 1 F 0 G 0 H 0 I 1
  • ${(j<>)...} une los elementos sin nada intermedio, por lo que se convierte en A0B0C0D0E1F0G0H0I1
  • ${...//(?0|1)}quitamos el ?0y 1de él para que se convierta en EI:
  • ${(s<>)...} dividirse en nada para obtener una matriz de un elemento por letra: EI
  • ${(j<,>)...}únete a aquellos con ,-> E, I.

esto es solo una simple fiesta ¿verdad?
fusion.slope

1
@ fusion.slope, No, es zshun shell diferente bash(y mucho más potente y con un diseño mucho mejor si me preguntas). bashha prestado sólo una pequeña fracción de la zsh'función s (como {1..4}, <<<, **/*) no los mencionados aquí, la mayoría de los bash' s características son tomados de otra forma de ksh.
Stéphane Chazelas

3

Otra solución awk :

awk 'NR==1{ split($0,a); next }   # capture and print `header` fields
     { for (i=1;i<=NF;i++)         # iterating through value fields `[0 1 ...]`
           if ($i) { printf "%s",(f?","a[i]:a[i]); f=1 } 
       f=0; print "" 
     }' file

La salida:

E,I
D
D
A
A,C,G
A,D,H
A,E,F,G

2

Aquí hay una solución en Perl:

use strict;

my @header = split /\s+/, <>;
<>; ## Skip blank line
while (<>) {
    my @flags = split /\s+/;
    my @letters = ();
    for my $i (0 .. scalar @flags - 1) {
        push @letters, $header[$i] if $flags[$i];
    }

    print join(',', @letters), "\n";
}

Funciona leyendo las columnas de encabezado en una matriz y luego, para cada fila de datos, copiando el nombre de la columna en una matriz de salida si la columna de datos coincidente se evalúa como verdadera. Los nombres de las columnas se imprimen separados por comas.


2

Un seduno por el gusto de hacerlo:

sed '
  s/ //g
  1{h;d;}
  G;s/^/\
/
  :1
    s/\n0\(.*\n\)./\
\1/
    s/\n1\(.*\n\)\(.\)/\2\
\1/
  t1
  s/\n.*//
  s/./&,/g;s/,$//'

Con GNU sed, puede hacerlo un poco más legible con:

sed -E '
  s/ //g # strip the spaces

  1{h;d} # hold the first line

  G;s/^/\n/ # append the held line and prepend an empty line so the
            # pattern space becomes <NL>010101010<NL>ABCDEFGHI we will
            # build the translated version in the part before the first NL
            # eating one character at a time off the start of the
            # 010101010 and ABCDEFGHI parts in a loop:
  :1
    s/\n0(.*\n)./\n\1/     # ...<NL>0...<NL>CDEFGHI becomes
                           # ...<NL>...<NL>DEFGHI (0 gone along with C)

    s/\n1(.*\n)(.)/\2\n\1/ # ...<NL>1...<NL>CDEFGHI becomes
                           # ...C<NL>...<NL>DEFGHI (1 gone but C moved to 
                           #                        the translated part)
  t1 # loop as long as any of those s commands succeed

  s/\n.*// # in the end we have "ADG<NL><NL>", strip those NLs

  s/./,&/2g # insert a , before the 2nd and following characters'

Una versión un poco más corta, suponiendo que siempre haya el mismo número de dígitos en cada línea:

sed -E '
  s/ //g
  1{H;d}
  G
  :1
    s/^0(.*\n)./\1/
    s/^1(.*\n)(.*\n)(.)/\1\3\2/
  t1
  s/\n//g
  s/./,&/2g'

Igual que el anterior, excepto que estamos intercambiando las partes traducidas e indexadas, lo que permite algunas optimizaciones.


si puede explicar sería bueno para la comunidad. Gracias de antemano
fusion.slope

1
@ fusion.slope, ver editar.
Stéphane Chazelas

agradable el bucle con el comando t1!
fusion.slope

1

python3

python3 -c '
import sys
header = next(sys.stdin).rstrip().split()
for line in sys.stdin:
  print(*(h*int(f) for (h, f) in zip(header, line.rstrip().split()) if int(f)), sep=",")

  ' <file
E,I
D
D
A
A,C,G
A,D,H
A,E,F,G

0

Solución pura de bash:

read -a h
while read -a r
do (
    for i in ${!r[@]}
    do 
        (( r[i] == 1 )) && y[i]=${h[i]}
    done
    IFS=,
    echo "${y[*]}")
done

3
Por favor explique cómo esto resuelve el problema.
Scott

Eso se deja como ejercicio para el lector. Suponiendo que el conocimiento básico de bash LESS="+/^ {3}Array" man bashdebería proporcionar toda la información necesaria para los arrays de bash. Puede editar la respuesta para agregar cualquier aclaración útil.
David Ongaro

-1
 void Main(string[] args)
        {
            int[,] numbers = new int[,]
            {
            {0, 0, 0, 0, 1, 0, 0, 0, 1},
            {0, 0, 0, 1, 0, 0, 0, 0, 0},
            {0, 0, 0, 1, 0, 0, 0, 0, 0},
            {1, 0, 0, 0, 0, 0, 0, 0, 0},
            {1, 0, 1, 0, 0, 0, 1, 0, 0},
            {1, 0, 0, 1, 0, 0, 0, 1, 0},
            {1, 0, 0, 0, 1, 1, 1, 0, 0}
            };
            string letters = "ABCDEFGHI";
            for (int row = 0; row < 7; row++)
            {
                for (int col = 0; col < 9; col++)
                {
                    if (numbers[row, col] == 1)
                        Console.Write(letters[col]);
                }
                Console.WriteLine();
            }
        }

3
Por favor explique qué hace esto y cómo funciona.
Scott

También el idioma, por favor.
fusion.slope
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.