如何删除重复的字符并仅在Perl中保留唯一的字符?

时间:2010-04-06 06:00:55

标签: regex perl string duplicates

如何删除重复的字符并仅保留唯一字符。 例如,我的输入是:

EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU

预期输出为:

EFUAH
UEH
UJHACDEF

我遇到perl -pe's/$1//g while/(.).*\/'这很精彩,但它甚至会删除输出中单个字符的出现。

11 个答案:

答案 0 :(得分:15)

可以使用positive lookahead

完成此操作
perl -pe 's/(.)(?=.*?\1)//g' FILE_NAME

使用的正则表达式是:(.)(?=.*?\1)

  • .:匹配任何字符。
  • 首先():记住匹配的 单个字符。
  • (?=...):+ ve lookahead
  • .*?:匹配
  • 之间的任何内容
  • \1:记住的比赛。
  • (.)(?=.*?\1):匹配并记住 任何字符 仅在 时再次出现 后来在字符串中。
  • s///:Perl的做法 取代
  • g:进行替换 全球......那是不会停止的 第一次替换。
  • s/(.)(?=.*?\1)//g:这会 从输入字符串中删除一个字符 只有后来再次出现该字符 在字符串中。

这将 维护输入中char的顺序,因为对于输入字符串中的每个唯一char,我们保留其 last < / em> 发生,而不是 第一次

为了保持相对顺序不变,我们可以在其中一条评论中执行KennyTM所说的内容:

  • 反转输入行
  • 按以前的方式进行替换
  • 打印前反转结果

Perl的一行是:

perl -ne '$_=reverse;s/(.)(?=.*?\1)//g;print scalar reverse;' FILE_NAME

由于我们在撤消后手动执行print,因此我们不使用-p标记,而是使用-n标记。

我不确定这是否是最好的单行代码。如果他们有更好的选择,我欢迎其他人编辑这个答案。

答案 1 :(得分:5)

如果Perl不是必须的,你也可以使用awk。这是针对awk发布的Perl one liners的有趣基准。对于具有300万++行的文件,awk快10秒以上

$ wc -l <file2
3210220

$ time awk 'BEGIN{FS=""}{delete _;for(i=1;i<=NF;i++){if(!_[$i]++) printf $i};print""}' file2 >/dev/null

real    1m1.761s
user    0m58.565s
sys     0m1.568s

$ time perl -n -e '%seen=();' -e 'for (split //) {print unless $seen{$_}++;}'  file2 > /dev/null

real    1m32.123s
user    1m23.623s
sys     0m3.450s

$ time perl -ne '$_=reverse;s/(.)(?=.*?\1)//g;print scalar reverse;' file2 >/dev/null

real    1m17.818s
user    1m10.611s
sys     0m2.557s

$ time perl -ne'my%s;print grep!$s{$_}++,split//' file2 >/dev/null

real    1m20.347s
user    1m13.069s
sys     0m2.896s

答案 2 :(得分:4)

perl -ne'my%s;print grep!$s{$_}++,split//'

答案 3 :(得分:4)

这是一个解决方案,我认为应该比前瞻性更快,但不是基于regexp并使用哈希表。

perl -n -e '%seen=();' -e 'for (split //) {print unless $seen{$_}++;}' 

它将每一行拆分为字符,并通过计算%see hashtable

内的外观来仅打印第一个外观

答案 4 :(得分:1)

Tie :: IxHash是一个存储哈希顺序的好模块(但可能很慢,如果速度很重要,你需要进行基准测试)。测试示例:

use Test::More 0.88;

use Tie::IxHash;
sub dedupe {
  my $str=shift;
  my $hash=Tie::IxHash->new(map { $_ => 1} split //,$str);
  return join('',$hash->Keys);
}

{
my $str='EFUAHUU';
is(dedupe($str),'EFUAH');
}

{
my $str='EFUAHHUU';
is(dedupe($str),'EFUAH');
}

{
my $str='UJUJHHACDEFUCU';
is(dedupe($str),'UJHACDEF');
}

done_testing();

答案 5 :(得分:1)

这看起来像是一个积极的lookbehind的经典应用程序,但不幸的是perl不支持这一点。实际上,我认为这样做(将字符串中字符的前一个文本与长度不确定的完整正则表达式匹配)只能用.NET正则表达式类完成。

然而,正向前瞻支持完全正则表达式,所以你需要做的就是反转字符串,应用正向前瞻(如unicornaddict所说):

perl -pe 's/(.)(?=.*?\1)//g' 

然后将其反转,因为没有反向,只会将重复的字符保留在一行的最后一个位置。

大规模编辑

我已经花费了过去半小时的时间,这看起来很有效,没有倒车

perl -pe 's/\G$1//g while (/(.).*(?=\1)/g)' FILE_NAME

我不知道是骄傲还是恐惧。我基本上做了积极的looakahead,然后用\ G指定的字符串替换字符串 - 这使得正则表达式引擎从匹配的最后一个地方(由pos()变量内部表示)开始匹配。

使用这样的测试输入:

  

aabbbcbbccbabb

     

EFAUUUUH

     

ABCBBBBD

     

DEEEFEGGH

     

AABBCC

输出如下:

  

ABC

     

EFAUH

     

ABCD

     

DEFGH

     

ABC

认为它正在运作......

解释 - 好的,如果上次我的解释不够清楚 - 前瞻将停止在重复变量的最后一个匹配[在代码中你可以做一个打印位置();在循环中检查]和s / \ G // g将删除它[你真的不需要/ g]。因此,在循环内,替换将继续删除,直到所有这些重复都被删除。当然,对于您的口味来说,这可能是一个过于强大的处理器......但是大多数基于正则表达式的解决方案也是如此。但是,反转/前瞻方法可能比这更有效。

答案 6 :(得分:1)

使用List::MoreUtils中的uniq:

perl -MList::MoreUtils=uniq -ne 'print uniq split ""'

答案 7 :(得分:1)

如果可以遇到的字符集受到限制,例如只有字母,那么最简单的解决方案就是用tr perl -p -e 'tr/a-zA-Z/a-zA-Z/s'
它将自己替换所有字母,使其他字符不受影响,并且/ s修饰符将挤压相同字符的重复出现(替换后),从而删除重复

我很糟糕 - 它只删除了相邻的外观。无视

答案 8 :(得分:0)

包含您列出的名为foo.txt

的数据的文件
python -c "print set(open('foo.txt').read())"

答案 9 :(得分:0)

从shell开始,这可以:

sed -e 's/$/<EOL>/ ; s/./&\n/g' test.txt | uniq | sed -e :a -e '$!N; s/\n//; ta ; s/<EOL>/\n/g'

单词:用<EOL>字符串标记每个换行符,然后将每个字符放在自己的一行上,然后使用uniq删除重复的行,然后删除所有的换行符,然后放入返回换行符而不是<EOL>标记。

我在论坛帖子中找到了-e :a -e '$!N; s/\n//; ta部分而且我不理解单独的-e :a部分或$!N部分,所以如果有人可以解释这些,我会不胜感激。

嗯,那只做连续重复;要消除所有重复项,你可以这样做:

cat test.txt | while read line ; do echo $line | sed -e 's/./&\n/g' | sort | uniq | sed -e :a -e '$!N; s/\n//; ta' ; done

但是,每行中的字符按字母顺序排列。

答案 10 :(得分:0)

use strict;
use warnings;

my ($uniq, $seq, @result);
$uniq ='';
sub uniq {
    $seq = shift;
    for (split'',$seq) {
    $uniq .=$_ unless $uniq =~ /$_/;
    }
    push @result,$uniq;
    $uniq='';
}

while(<DATA>){
   uniq($_);
}
print @result;

__DATA__
EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU

输出:

EFUAH
UEH
UJHACDEF