Crea elenchi di parole in base a numeri binari


12

Ho una matrice che assomiglia a quanto segue:

Input :

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  

E vorrei estrarre per ogni riga l'elenco di lettere corrispondente al valore 1.

Uscita :

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

Ho provato a dividere l'intestazione e ad abbinare le parole ai numeri ma non ci sono riuscito.

Risposte:


12

In 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 }

6
può anche usareNR == 1 { split($0,values) }
Sundeep l'

Questo sta saltando la seconda linea. Considera di mettere un nextalla fine della prima riga in modo da non dover testare una condizione opposta per le righe successive.
Ed Morton,

1
Sembra che il testo di input originale avesse una riga vuota in più, che ho codificato. Da allora è stato modificato, quindi NR > 2passa a NR > 1.
Jeff Schaller

1
Grazie per il consiglio "golf", Sundeep! Penso di preferire il ciclo esplicito "for" poiché si allinea visivamente / logicamente con il ciclo "for" nel corpo.
Jeff Schaller

1
@ fusion.slope, o passa l'intero codice in un argomento tra virgolette singole awk, oppure incolla il codice in un file ed eseguilo conawk -f that.script.file input-file
Jeff Schaller

6

Un altro 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
  • -aopzione per dividere la linea di input su spazi bianchi, disponibile in @Farray
  • if($. == 1){ @h=@F } salva l'intestazione se la prima riga
  • @i = grep {$F[$_]==1} (0..$#F) salva indice se la voce è 1
  • print join ",",@h[@i]stampa solo quegli indici dall'array di intestazione usando ,come separatore

4

Ancora per divertimento, una zshversione:

{
   read -A a  &&
   while read -A b; do
     echo ${(j<,>)${(s<>)${(j<>)a:^b}//(?0|1)}}
   done
} < file
  • ${a:^b} comprime i due array in modo da ottenere A 0 B 0 C 0 D 0 E 1 F 0 G 0 H 0 I 1
  • ${(j<>)...} unisce gli elementi senza nulla in mezzo e diventa A0B0C0D0E1F0G0H0I1
  • ${...//(?0|1)}lo eliminiamo ?0e 1diventa EI:
  • ${(s<>)...} diviso su nulla per ottenere una matrice di un elemento per lettera: EI
  • ${(j<,>)...}unisciti a quelli con ,-> E, I.

questo è solo un semplice colpo giusto?
fusion.slope,

1
@ fusion.slope, No, quello è zshun guscio diverso da bash(e molto più potente, e con un design molto migliore se me lo chiedi). bashha preso in prestito solo una piccola frazione della zsh'funzione s (come {1..4}, <<<, **/*), non quelli menzionati qui, La maggior parte di bash' s caratteristiche sono comunque presi in prestito da ksh.
Stéphane Chazelas,

3

Un'altra soluzione 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

Il risultato:

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

2

Ecco una soluzione in 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";
}

Funziona leggendo le colonne di intestazione in un array e quindi, per ogni riga di dati, copiando il nome della colonna in un array di output se la colonna di dati corrispondente viene valutata come vera. I nomi delle colonne vengono quindi stampati separati da virgola.


2

Uno sedper il gusto di farlo:

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, puoi renderlo un po 'più leggibile 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 versione leggermente più corta, supponendo che ci sia sempre lo stesso numero di cifre su ogni riga:

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'

Come sopra, tranne che stiamo scambiando le parti tradotte e indicizzate che consentono alcune ottimizzazioni.


se puoi spiegare sarebbe positivo per la comunità. Grazie in anticipo
fusion.slope,

1
@ fusion.slope, vedi modifica.
Stéphane Chazelas,

bello il ciclo con il 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

Pura soluzione di 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
Spiega come questo risolve il problema.
Scott,

Questo è lasciato come esercizio per il lettore. Supponendo che la conoscenza di base di bash LESS="+/^ {3}Array" man bashdovrebbe fornire tutte le informazioni necessarie per gli array di bash. Sei libero di modificare la risposta per aggiungere eventuali chiarimenti utili.
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
Spiega cosa fa questo e come funziona.
Scott,

anche la lingua, per favore.
fusion.slope,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.