如何删除重复的字符并仅保留唯一字符。 例如,我的输入是:
EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU
预期输出为:
EFUAH
UEH
UJHACDEF
我遇到perl -pe's/$1//g while/(.).*\/'
这很精彩,但它甚至会删除输出中单个字符的出现。
答案 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