Удаление строк и столбцов с нулями

Как удалить строки (строки) и столбцы в текстовом файле, которые содержат все нули. Например, у меня есть файл:

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 Мб файла данных, и он работал отлично, это было не очень быстро, но он не потреблял много памяти.