# Create lists of words according to binary numbers

I have a matrix that looks like following:

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
``````

And I would like to extract for each row the list of letter corresponding to the value 1.

Output:

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

I have tried to split the header and to match the words with numbers but I failed.

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 }
``````
• can also use `NR == 1 { split(\$0,values) }` – Sundeep Oct 11 '17 at 15:54
• That's skipping the 2nd line. Consider putting a `next` at the end of the first line so you don't need to test an opposite condition for subsequent lines. – Ed Morton Oct 11 '17 at 16:11
• Appears the original input text had an extra blank line in it, which I coded for. It's since been edited out, so just change `NR > 2` to `NR > 1`. – Jeff Schaller Oct 11 '17 at 16:12
• Thank you for the "golfing" tip, Sundeep! I think I prefer the explicit 'for' loop as it lines up visually/logically with the 'for' loop in the body. – Jeff Schaller Oct 11 '17 at 16:16
• @fusion.slope, either pass the entire code in a single-quoted argument to `awk`, or paste the code into a file and run it with `awk -f that.script.file input-file` – Jeff Schaller Oct 11 '17 at 16:17

Another one with `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
``````
• `-a` option to split input line on white-spaces, available in `@F` array
• `if(\$. == 1){ @h=@F }` save the header if first line
• `@i = grep {\$F[\$_]==1} (0..\$#F)` save index if entry is `1`
• `print join ",",@h[@i]` print only those index from header array using `,` as separator

Still for the fun of it, a `zsh` version:

``````{
echo \${(j<,>)\${(s<>)\${(j<>)a:^b}//(?0|1)}}
done
} < file
``````
• `\${a:^b}` zips the two arrays, so you get A 0 B 0 C 0 D 0 E 1 F 0 G 0 H 0 I 1
• `\${(j<>)...}` joins the elements with nothing in between so it becomes A0B0C0D0E1F0G0H0I1
• `\${...//(?0|1)}` we strip the `?0` and `1` from it so it becomes EI:
• `\${(s<>)...}` split on nothing to get an array of one element per letter: E I
• `\${(j<,>)...}` join those with `,` -> E,I.
• this is just a simple bash right? – fusion.slope Oct 12 '17 at 13:13
• @fusion.slope, No, that's `zsh`, a different shell from `bash` (and much more powerful, and with a much better design if you ask me). `bash` has borrowed only a tiny fraction of `zsh`'s feature (like `{1..4}`, `<<<`, `**/*`) not the ones mentioned here, Most of `bash`'s features are otherwise borrowed from `ksh`. – Stéphane Chazelas Oct 12 '17 at 13:34

Another awk solution:

``````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
``````

The output:

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

Here is a solution 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) {
}

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

It works by reading the header columns into an array and then, for each data row, copying the column name to an output array if the matching data column evaluates as true. The column names are then printed comma-separated.

A `sed` one for the fun of it:

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

With GNU `sed`, you can make it a bit more legible with:

``````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'
``````

A slightly shorter version, assuming there's always the same number of digits on each line:

``````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'
``````

Same as above except we're swapping the translated and index parts which allows for some optimisations.

• if you can explain would be good for the community. Thanks in advance – fusion.slope Oct 12 '17 at 11:51
• @fusion.slope, see edit. – Stéphane Chazelas Oct 12 '17 at 12:00
• nice the loop with t1 command! – fusion.slope Oct 12 '17 at 12:05

`python3`

``````python3 -c '
import sys
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
``````

Pure bash solution:

``````read -a h
do (
for i in \${!r[@]}
do
(( r[i] == 1 )) && y[i]=\${h[i]}
done
IFS=,
echo "\${y[*]}")
done
``````
• Please explain how this solves the problem. – Scott Oct 12 '17 at 3:22
• That is left as an exercise for the reader. Assuming basic bash knowledge `LESS="+/^ {3}Array" man bash` should give all information needed for bash arrays. You are free to edit the answer to add any any helpful clarification. – David Ongaro Oct 13 '17 at 1:46
`````` 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();
}
}
``````
• Please explain what this does and how it works. – Scott Oct 12 '17 at 3:19
• also the language, please. – fusion.slope Oct 12 '17 at 7:44