【问题标题】:How to get only uniq column values from a line using linux or perl?如何使用 linux 或 perl 从一行中仅获取唯一的列值?
【发布时间】:2017-03-03 13:00:33
【问题描述】:

我有一个类似下面的文件

1 B B C D B
2 K B D D K
1 B B C D B
2 K B D D K
1 B B C D B
2 K B D D K

我希望输出看起来像这样

1 B C D
2 K B D
1 B C D
2 K B D
1 B C D
2 K B D

排序命令不起作用,所以我尝试编写一个 Perl 程序 -

use strict;
use Data::Dumper; 

my $file1 = <$ARGV[0]>;

open (IF2, "$file1") || die "Cannot open the file\n";

open (OUT, ">$file1.out") || die "Cannot open the out file\n";



my $k = 0;my $i=0;
my @line;
my $m;
my @line2;



while ( chomp($m = <IF2>) ) {   
    my $count = 0;
    @line2 = split(/\t/,$m);#<stdin>;
    my $l = length @line2;print $l;<stdin>;
    for (my $x = 0; $x < $l;$x++) {
        my $k = 0;
        for (my $y = 0;$y < $l; $y) {
             $i++;
    #       
            if ($count == 0) 
            {
                print OUT "\t$line2[$x]";
                $count++;
            }

            if ($count != 0 && $x != $y)
            {
                if ($line2[$x] eq $line2[$y])
                {
                     $k++;
                }

            }
        }
        if ($k == 0)
        {
            print OUT "\t$line2[$x]";
        }
    }
  print OUT "\n";   
  }



  print $i;

  close IF2;
  close OUT;

但它没有用。 有人可以帮忙吗?

【问题讨论】:

  • 这非常非常复杂。您可以使用 Perl,有多种处理文本的方法,所以请编写 Perl,而不是 C。当您阅读该行时,split 它,然后通过 uniqList::Utils 模块传递该列表
  • 等等......你不想要“uniq”元素——你的输出有多个Bs、Ds等。你只想删除相邻重复,对吗?如果是这样,我上面的评论是不对的,uniq只会留下一个B,一个D,等等

标签: perl unique


【解决方案1】:

注意问题中的输入输出示例已编辑:现在数据与标题一致,要求删除所有重复项。 (请参阅原始问题以了解它的外观。)我也将保留原始问题的答案,直到我们收到海报的消息。


我想先做一个一般性的评论。

您发布的代码是用 Perl 编写 C 风格程序的诚实尝试。我建议不要这样做,而是学习如何使用 Perl。它确实需要一些时间和精力的投入,但它很快就会得到回报。正如您在下面看到的,它使许多事情变得无比简单。

即便如此,发布的代码还是有问题,但我现在无法编写代码审查。

已编辑的问题

我们需要从每一行中删除所有重复项。一种方法:用空格分割行,然后从列表中删除重复项,这是一项具有现成解决方案的标准任务。

use warnings;
use strict;
use List::MoreUtils qw(uniq);

my $file = '...';
my $fileout = '...';

open my $fh,     '<', $filen   or die "Can't open $file: $!";
open my $fh_out, '>', $fileout or die "Can't open $fileout: $!";

while (<$fh>) 
{
    my @unique = uniq split;
    print $fh_out "@unique\n";
}
close $fh;
close $fh_out;

当在元素之间插入空格(或$" 中的任何内容)时,通过打印带引号的数组来恢复带有空格的行。另一个选项是join 结果列表

my $res = join ' ', uniq split;
print $fh_out $res, "\n";

或者只是print $fh_out join(' ', uniq split), "\n";

这使用来自List::MoreUtils 模块的uniq。来自uniq 的注释

返回列表中元素的顺序与LIST中的相同。

一旦提到List::MoreUtils,也要注意核心模块List::Util

单行版本

perl -MList::MoreUtils=uniq -lne'print join " ", uniq split' input > output

perl -MList::MoreUtils=uniq -lane'print join " ", uniq @F' input > output

Command switches in perlrun


原始问题(在编辑历史中查看)

我在回答问题时相信这是输入

1 B B C D B 2 K B D D K 1 B B C D B 2 K B D D K 1 B B C D B 2 K B D D K

这是想要的输出

1 B C D 2 K B D 1 B C D 2 K B D 1 B C D 2 K B D

根据您想要的输出,您只想删除 adjacent 重复项(而不是标题所说的“uniq”)。

为此,您可以使用正则表达式的功能来匹配重复的模式,方法是使用backreferences。首先,我们需要去除所有空格,最后将它们放回原处。例如

use warnings;
use strict;

my $file = '...';
my $fileout = '...';

open my $fh,     '<', $filen   or die "Can't open $file: $!";
open my $fh_out, '>', $fileout or die "Can't open $fileout: $!";

while (my $line = <$fh>) 
{
    $line =~ s/\s*//g;       # remove spaces  /
    $line =~ s/(.)\1+/$1/g;  # remove adjacent duplicates
    $line =~ s/(.)/$1 /g;    # restore space
    print $fh_out $line;
}
close $fh;
close $fh_out;

. 匹配任何字符,如果需要,将其替换为更严格的字符(例如 \w,用于“单词”字符)。见perlretut。请注意,我们无法在替换的同时恢复空间(如s/(.)\1+/$1 /g),因为非重复字符不匹配并且不会取回空间。

这可以通过更简洁的方式完成。


单行版本

perl -pe's/\s*//g; s/(.)\1+/$1/g; s/(.)/$1 /g' input > output

Command switches in perlrun

【讨论】:

  • 建议的单行对我不起作用(perl 5.18),返回没有重复的单行正确折叠('1 B C D B 2 K B D K 1 B C D B 2 K B D K 1 B C D B 2 K B D K'),请检查
  • “根据您想要的输出,您只想删除相邻的重复项” 我不这么认为。 1 B B C D B 被缩减为 1 B C D,因此第一个之后的所有 B 字符都将被删除,无论它们是否相邻。
  • @Borodin 是的,在您编辑问题以将该行分成多行之后。最初的问题有一行很长,我没想到它应该是多行。这是否决票的原因吗?问题被改写了,现在这个答案似乎无效?
  • @feast 您正在调查一个已更改的问题。点击“Edited”查看以前的编辑——显示了一行输入和一行输出,我认为这是输入和输出的代表行。这篇文章回答了那个的问题。
  • @feast 我现在也添加了对 this 问题的答案。 (虽然这可能是发帖人有意为之,但这绝不是显而易见的。他们最初确实发布了一行输入和一行输出,其中确实有重复的字符,因此表明希望只删除相邻的欺骗。这更难,顺便说一句。)
【解决方案2】:

我建议这样的事情。它查找所有出现的空白,然后是非空白,并检查在当前行之前是否已经看到过非空白。如果之前看到过非空格,则删除匹配的子字符串,否则保持不变

use strict;
use warnings 'all';

while ( <DATA> ) {
    my %seen;
    s/(\s+(\S+))/ $seen{$2}++ ? '' : $1 /eg;
    print;
}

__DATA__
1 B B C D B
2 K B D D K
1 B B C D B
2 K B D D K
1 B B C D B
2 K B D D K

输出

1 B C D
2 K B D
1 B C D
2 K B D
1 B C D
2 K B D

这可以像这样在单行中完成

perl -pe 'my %s; s/(\s+(\S+))/ $s{$2}++ ? "" : $1 /eg' myfile

【讨论】:

  • 这个单行也给出了错误的结果: perl -pe 's/(\s+(\S+))/ $seen{$2}++ ? "" : $1 /eg' input.txt: "1 B C D 2 K 1 2 1 2 "
  • @feast:已修复。
猜你喜欢
  • 1970-01-01
  • 2019-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-12
  • 1970-01-01
相关资源
最近更新 更多