Удаление строк и столбцов с нулями
Как удалить строки (строки) и столбцы в текстовом файле, которые содержат все нули. Например, у меня есть файл:
1 0 1 0 1
0 0 0 0 0
1 1 1 0 1
0 1 1 0 1
1 1 0 0 0
0 0 0 0 0
0 0 1 0 1
Я хочу удалить 2-ю и 4-ю строку, а также 2-ю колонку. Вывод должен выглядеть так:
1 0 1 1
1 1 1 1
0 1 1 1
1 1 0 0
0 0 1 1
Я могу сделать это с помощью sed и egrep
sed '/0 0 0 0/d' or egrep -v '^(0 0 0 0 )$'
для строк с нулями, но это было слишком неудобно для файлов с тысячами колонн. Я понятия не имею, как удалить столбец со всеми нулями, 2-й столбец здесь.
10 ответов
другой вариант awk:
awk '{show=0; for (i=1; i<=NF; i++) {if ($i!=0) show=1; col[i]+=$i;}} show==1{tr++; for (i=1; i<=NF; i++) vals[tr,i]=$i; tc=NF} END{for(i=1; i<=tr; i++) { for (j=1; j<=tc; j++) { if (col[j]>0) printf("%s%s", vals[i,j], OFS)} print ""; } }' file
Развернутом Виде:
awk '{
show=0;
for (i=1; i<=NF; i++) {
if ($i != 0)
show=1;
col[i]+=$i;
}
}
show==1 {
tr++;
for (i=1; i<=NF; i++)
vals[tr,i]=$i;
tc=NF
}
END {
for(i=1; i<=tr; i++) {
for (j=1; j<=tc; j++) {
if (col[j]>0)
printf("%s%s", vals[i,j], OFS)
}
print ""
}
}' file
попробуйте это:
perl -n -e '$_ !~ /0 0 0 0/ and print' data.txt
или просто:
perl -n -e '/1/ and print' data.txt
здесь data.txt
содержит ваши данные.
В Windows, используйте двойные кавычки:
perl -n -e "/1/ and print" data.txt
решение на Perl. Он сохраняет все ненулевые строки в памяти для печати в конце, потому что он не может сказать, какие столбцы будут ненулевыми, прежде чем он обработает весь файл. Если вы получите Out of memory
, вы можете хранить только номера строк, которые вы хотите вывести, и обрабатывать файл снова во время печати строк.
#!/usr/bin/perl
use warnings;
use strict;
my @nonzero; # What columns where not zero.
my @output; # The whole table for output.
while (<>) {
next unless /1/;
my @col = split;
$col[$_] and $nonzero[$_] ||= 1 for 0 .. $#col;
push @output, \@col;
}
my @columns = grep $nonzero[$_], 0 .. $#nonzero; # What columns to output.
for my $line (@output) {
print "@{$line}[@columns]\n";
}
все вместе:
$ awk '{for (i=1; i<=NF; i++) {if ($i) {print; next}}}' file | awk '{l=NR; c=NF; for (i=1; i<=c; i++) {a[l,i]=$i; if ($i) e[i]++}} END{for (i=1; i<=l; i++) {for (j=1; j<=c; j++) {if (e[j]) printf "%d ",a[i,j] } printf "\n"}}'
это делает проверку строки:
$ awk '{for (i=1; i<=NF; i++) {if ($i) {print; next}}}' file
1 0 1 1
1 0 1 0
1 0 0 1
он петляет по всем полям линии. Если какой-либо из них "true" (означает не 0), он печатает строку (print
) и переходит к следующей строке (next
).
это делает проверку столбца:
$ awk '{l=NR; c=NF;
for (i=1; i<=c; i++) {
a[l,i]=$i;
if ($i) e[i]++
}}
END{
for (i=1; i<=l; i++){
for (j=1; j<=c; j++)
{if (e[j]) printf "%d ",a[i,j] }
printf "\n"
}
}'
он в основном сохраняет все данные в a
массив, l
количество строк, c
количество колонок. e
- сохранение массива, если столбец имеет любое значение, отличное от 0 или нет. Затем он петляет и печатает все поля, когда e
индекс массива установлен, то есть если этот столбец имеет ненулевое значение.
тест
$ cat a
1 0 1 0 1
0 0 0 0 0
1 1 1 0 1
0 1 1 0 1
1 1 0 0 0
0 0 0 0 0
0 0 1 0 1
$ awk '{for (i=1; i<=NF; i++) {if ($i) {print; next}}}' a | awk '{l=NR; c=NF; for (i=1; i<=c; i++) {a[l,i]=$i; if ($i) e[i]++}} END{for (i=1; i<=l; i++) {for (j=1; j<=c; j++) {if (e[j]) printf "%d ",a[i,j] } printf "\n"}}'
1 0 1 1
1 1 1 1
0 1 1 1
1 1 0 0
0 0 1 1
предыдущий вход:
$ cat file
1 0 1 1
0 0 0 0
1 0 1 0
0 0 0 0
1 0 0 1
$ awk '{for (i=1; i<=NF; i++) {if ($i) {print; next}}}' file | awk '{l=NR; c=NF; for (i=1; i<=c; i++) {a[l,i]=$i; if ($i) e[i]++}} END{for (i=1; i<=l; i++) {for (j=1; j<=c; j++) {if (e[j]) printf "%d ",a[i,j] } printf "\n"}}'
1 1 1
1 1 0
1 0 1
вместо того, чтобы хранить строки в памяти, эта версия сканирует файл дважды: один раз, чтобы найти "нулевые столбцы", и снова, чтобы найти" нулевые строки " и выполнить вывод:
awk '
NR==1 {for (i=1; i<=NF; i++) if ($i == 0) zerocol[i]=1; next}
NR==FNR {for (idx in zerocol) if ($idx) delete zerocol[idx]; next}
{p=0; for (i=1; i<=NF; i++) if ($i) {p++; break}}
p {for (i=1; i<=NF; i++) if (!(i in zerocol)) printf "%s%s", $i, OFS; print ""}
' file file
1 0 1 1
1 1 1 1
0 1 1 1
1 1 0 0
0 0 1 1
программа ruby: ruby имеет хороший метод массива transpose
#!/usr/bin/ruby
def remove_zeros(m)
m.select {|row| row.detect {|elem| elem != 0}}
end
matrix = File.readlines(ARGV[0]).map {|line| line.split.map {|elem| elem.to_i}}
# remove zero rows
matrix = remove_zeros(matrix)
# remove zero rows from the transposed matrix, then re-transpose the result
matrix = remove_zeros(matrix.transpose).transpose
matrix.each {|row| puts row.join(" ")}
следующий скрипт делает два прохода. Во время первого прохода он сохраняет номера строк, которые должны быть опущены из выходных данных, и индексы столбцов, которые должны быть включены в выходные данные. Во втором проходе он выводит эти строки и столбцы. Я думаю, что это должно обеспечить близкий к наименьшему возможному следу памяти, который может иметь значение, если вы имеете дело с большими файлами.
#!/usr/bin/env perl
use strict;
use warnings;
filter_zeros(\*DATA);
sub filter_zeros {
my $fh = shift;
my $pos = tell $fh;
my %nonzero_cols;
my %zero_rows;
while (my $line = <$fh>) {
last unless $line =~ /\S/;
my @row = split ' ', $line;
my @nonzero_idx = grep $row[$_], 0 .. $#row;
unless (@nonzero_idx) {
$zero_rows{$.} = undef;
next;
}
$nonzero_cols{$_} = undef for @nonzero_idx;
}
my @matrix;
{
my @idx = sort {$a <=> $b } keys %nonzero_cols;
seek $fh, $pos, 0;
local $. = 0;
while (my $line = <$fh>) {
last unless $line =~ /\S/;
next if exists $zero_rows{$.};
print join(' ', (split ' ', $line)[@idx]), "\n";
}
}
}
__DATA__
1 0 1 0 1
0 0 0 0 0
1 1 1 0 1
0 1 1 0 1
1 1 0 0 0
0 0 0 0 0
0 0 1 0 1
выход:
1 0 1 1 1 1 1 1 0 1 1 1 1 1 0 0 0 0 1 1
немного неортодоксальное решение, но быстрое, как ад, и небольшое потребление памяти:
perl -nE's/\s+//g;$m|=$v=pack("b*",$_);push@v,$v if$v!~/0/}{$m=unpack("b*",$m);@m=split//,$m;@m=grep{$m[$_]eq"1"}0..$#m;say"@{[(split//,unpack(q(b*),$_))[@m]]}"for@v'
Это мое решение awk. Она будет работать с переменным числом строк и столбцов.
#!/usr/bin/gawk -f
BEGIN {
FS = " "
}
{
for (c = 1; c <= NF; ++c) {
v = $c
map[c, NR] = v
ctotal[c] += v
rtotal[NR] += v
}
fields[NR] = NF
}
END {
for (r = 1; r <= NR; ++r) {
if (rtotal[r]) {
append = 0
f = fields[r]
for (c = 1; c <= f; ++c) {
if (ctotal[c]) {
if (append) {
printf " " map[c, r]
} else {
printf map[c, r]
append = 1
}
}
}
print ""
}
}
}
С моей макушки...
проблема в колонках. Как вы узнаете, что столбец - это все нули, пока вы не прочитаете весь файл?
Я думаю, вам нужен массив столбцов с каждым массивом, являющимся столбцом. Вы можете нажать на суммы. Массив массивов.
фокус в том, чтобы пропустить строки, содержащие все нули, когда вы их читаете:
#! /usr/bin/env perl
#
use strict;
use warnings;
use autodie;
use feature qw(say);
use Data::Dumper;
my @array_of_columns;
for my $row ( <DATA> ) {
chomp $row;
next if $row =~ /^(0\s*)+$/; #Skip zero rows;
my @columns = split /\s+/, $row;
for my $index ( (0..$#columns) ) {
push @{ $array_of_columns[$index] }, $columns[$index];
}
}
# Remove the columns that contain nothing but zeros;
for my $column ( (0..$#array_of_columns) ) {
my $index = $#array_of_columns - $column;
my $values = join "", @{ $array_of_columns[$index] };
if ( $values =~ /^0+$/ ) {
splice ( @array_of_columns, $index, 1 );
}
}
say Dumper \@array_of_columns;
__DATA__
1 0 1 0 1
0 0 0 0 0
1 1 1 0 1
0 1 1 0 1
1 1 0 0 0
0 0 0 0 0
0 0 1 0 1
конечно, вы можете использовать Array:: Transpose что будет транспонируйте свой массив, что делает вещи намного проще.
это действительно сложный и сложный вопрос.. поэтому, чтобы решить, нам тоже нужно быть хитрыми:) в моей версии я зависим от обучения сценарию, каждый раз, когда мы читаем новую строку, мы проверяем возможность опустить новое поле, и если обнаружено новое изменение, мы начинаем заново.
проверка и запуск процесса не должны повторяться так часто, как у нас будет несколько раундов до тех пор, пока мы не получим постоянное число полей, чтобы опустить или ноль, то мы опускаем каждую строку нулевое значение в определенном позиция.
#! /usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
open my $fh, '<', 'file.txt' or die $!;
##open temp file for output
open my $temp, '>', 'temp.txt' or die $!;
##how many field you have in you data
##you can increase this by one if you have more fields
my @fields_to_remove = (0,1,2,3,4);
my $change = $#fields_to_remove;
while (my $line = <$fh>){
if ($line =~ /1/){
my @new = split /\s+/, $line;
my $i = 0;
for (@new){
unless ($_ == 0){
@fields_to_remove = grep(!/$i/, @fields_to_remove);
}
$i++;
}
foreach my $field (@fields_to_remove){
$new[$field] = 'x';
}
my $new = join ' ', @new;
$new =~ s/(\s+)?x//g;
print $temp $new . "\n";
##if a new change detected start over
## this should repeat for limited time
## as the script keeps learning and eventually stop
if ($#fields_to_remove != $change){
$change = $#fields_to_remove;
seek $fh, 0, 0;
close $temp;
unlink 'temp.txt';
open $temp, '>', 'temp.txt';
}
} else {
##nothing -- removes 0 lines
}
}
### this is just for showing you which fields has been removed
print Dumper \@fields_to_remove;
Я тестировал с 9 полями 25 Мб файла данных, и он работал отлично, это было не очень быстро, но он не потреблял много памяти.