6

如何删除包含所有零的文本文件中的行(行)和列。例如,我有一个文件:

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 )$'

对于带有零的行,但对于具有数千列的文件来说太不方便了。我不知道如何删除全为零的列,这里是第二列。

4

12 回答 12

4

此版本不是将行存储在内存中,而是扫描文件两次:一次查找“零列”,再次查找“零行”并执行输出:

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(" ")}
于 2013-08-06T13:07:30.040 回答
4

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";
}
于 2013-08-06T11:58:58.813 回答
3

另一个 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
于 2013-08-06T12:55:21.393 回答
3

全部一起:

$ 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

它循环遍历该行的所有字段。如果其中任何一个为“真”(表示非 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 
于 2013-08-06T11:44:23.120 回答
3

尝试这个:

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
于 2013-08-06T11:45:55.863 回答
1

Little bit unorthodox solution but fast as hell and small memory consumption:

perl -nE's/\s+//g;$m|=$v=pack("b*",$_);push@v,$v if$v!~/\000/}{$m=unpack("b*",$m);@m=split//,$m;@m=grep{$m[$_]eq"1"}0..$#m;say"@{[(split//,unpack(q(b*),$_))[@m]]}"for@v'
于 2013-08-06T15:48:49.067 回答
1

这是一个真正棘手且具有挑战性的问题..所以为了解决我们也需要变得棘手:) 在我的版本中,我依赖于脚本学习,每次我们阅读新行时,我们都会检查是否有新的字段可能被省略,如果检测到新的变化,我们重新开始。

检查和重新开始的过程不应该如此频繁地重复,因为我们将有几轮,直到我们得到恒定数量的字段要省略或为零,然后我们在特定位置省略每一行的零值。

#! /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 个字段的 25mb 数据文件进行了测试,它运行良好,虽然速度不是很快,但也没有消耗太多内存。

于 2013-08-08T01:47:41.290 回答
1

从我的头顶上...

问题是列。在读入整个文件之前,如何知道一列是否全为零?

我在想你需要一个列数组,每个数组都是列。您可以推入金额。数组数组。

诀窍是在阅读时跳过包含全零的行:

#! /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来转置您的数组,从而使事情变得更容易。

于 2013-08-06T15:03:47.783 回答
1

以下脚本也进行了两次传递。在第一次传递期间,它保存要从输出中省略的行号和应包含在输出中的列索引。在第二遍中,它输出那些行和列。我认为这应该提供接近最小可能的内存占用,如果您正在处理大文件,这可能很重要。

#!/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
于 2013-08-06T15:06:14.700 回答
1

这是我的 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 ""
        }
    }
}
于 2013-08-06T16:17:46.790 回答
0

我使用 grep 和 cut 的紧凑且兼容大文件的替代方案。唯一的缺点:由于 for 循环,对于大文件来说很长。

# Remove constant lines using grep
    $ grep -v "^[0 ]*$\|^[1 ]*$" $fIn > $fTmp

# Remove constant columns using cut and wc

    $ nc=`cat $fTmp | head -1 | wc -w` 
    $ listcol=""
    $ for (( i=1 ; i<=$nc ; i++ ))
    $ do
    $   nitem=`cut -d" " -f$i $fTmp | sort | uniq | wc -l`
    $   if [ $nitem -gt 1 ]; then listcol=$listcol","$i ;fi
    $ done
    $ listcol2=`echo $listcol | sed 's/^,//g'`
    $ cut -d" " -f$listcol2 $fTmp | sed 's/ //g' > $fOut
于 2018-12-12T16:52:52.230 回答
0

可以通过以下方式检查行:awk '/[^0[:blank:]]/' file

它只是说明一行是否包含任何不同于0<blank> 字符的字符,然后打印该 line

如果您现在想检查列,那么我建议改编格伦杰克曼的回答

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}
    /[^0[:blank:]]/ {for (i=1; i<=NF; i++) if (i in zerocol) $i=""; print}
' file file
于 2018-12-12T18:39:20.693 回答