Как удалить повторяющиеся символы и сохранить уникальный только в Perl?
Как удалить повторяющиеся символы и сохранить только один.
Например, мой ввод:
EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU
Ожидаемый результат:
EFUAH
UEH
UJHACDEF
Я наткнулся на perl -pe's/$1//g while/(.).*\/'
, который замечателен, но он удаляет даже одиночное появление символа на выходе.
Ответы
Ответ 1
Это можно сделать, используя положительный прогноз:
perl -pe 's/(.)(?=.*?\1)//g' FILE_NAME
Используемое регулярное выражение: (.)(?=.*?\1)
-
.
: для соответствия любому char.
- сначала
()
: помните совпадающие
одиночный char.
-
(?=...)
: + ve lookahead
-
.*?
: чтобы соответствовать чему-либо между
-
\1
: запоминаемое совпадение.
-
(.)(?=.*?\1)
: матч и запоминание
любой char , только если снова появится
позже в строке.
-
s///
: способ Perl выполнить
замена.
-
g
: сделать замену
глобально... это не останавливается после
первая подстановка.
-
s/(.)(?=.*?\1)//g
: это будет
удалите char из строки ввода
только если этот char появится снова позже
в строке.
Это будет не поддерживать порядок char на входе, потому что для каждого уникального char во входной строке мы сохраняем его last, а не сначала.
Чтобы сохранить относительный порядок неповрежденным, мы можем сделать то, что KennyTM
говорит в одном из комментариев:
- обратная строка ввода
- выполните замену как раньше
- отмените результат перед печатью
Перл для этой строки:
perl -ne '$_=reverse;s/(.)(?=.*?\1)//g;print scalar reverse;' FILE_NAME
Поскольку мы делаем print
вручную после разворота, мы не используем флаг -p
, но используем флаг -n
.
Я не уверен, что это лучший лайнер для этого. Я приветствую других, чтобы отредактировать этот ответ, если у них есть лучшая альтернатива.
Ответ 2
Вот решение, которое, я думаю, должно работать быстрее, чем просмотр, но не основано на регулярном выражении и использует хеш-таблицу.
perl -n -e '%seen=();' -e 'for (split //) {print unless $seen{$_}++;}'
Он разбивает каждую строку на символы и печатает только первое появление, считая появления внутри% seen hashtable
Ответ 3
Если Perl не является обязательным, вы также можете использовать awk. вот веселый бенчмарк на Perl, один лайнер, отправленный против awk. awk на 10 + секунд быстрее для файла с 3миллионными ++ строками
$ 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
Ответ 4
perl -ne'my%s;print grep!$s{$_}++,split//'
Ответ 5
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();
Ответ 6
Это выглядит как классическое приложение положительного lookbehind, но, к сожалению, perl не поддерживает это. На самом деле, делая это (совпадающий с предыдущим текстом символа в строке с полным регулярным выражением, длина которого неопределима), я могу сделать только с классами .NET regex.
Однако положительный lookahead поддерживает полные регулярные выражения, поэтому все, что вам нужно сделать, это изменить строку, применить положительный результат (например, unicornaddict said):
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
Я думаю, что он работает...
Объяснение - Хорошо, если мое объяснение в прошлый раз не было достаточно ясным - просмотр будет идти и останавливаться в последнем совпадении дублирующей переменной [в коде вы можете сделать print pos(); внутри цикла, чтобы проверить], и s/\ G//g удалит его [вам действительно не нужно /g действительно]. Таким образом, в цикле замещение будет продолжаться, пока все эти дубликаты не будут забиты. Конечно, это может быть слишком интенсивным для вашего вкуса. Но большинство решений на основе регулярных выражений вы увидите. Однако метод реверсирования/просмотра может быть более эффективным, чем это.
Ответ 7
Используйте uniq из List:: MoreUtils:
perl -MList::MoreUtils=uniq -ne 'print uniq split ""'
Ответ 8
Если набор символов, которые могут встречаться, ограничен, например. только буквы, тогда самое простое решение будет с tr
perl -p -e 'tr/a-zA-Z/a-zA-Z/s'
Он заменит все буквы сам по себе, оставив другие символы незатронутыми, и/или модификатор сжимает повторяющиеся вхождения одного и того же символа (после замены), тем самым удаляя дубликаты
Мне плохо - он удаляет только прилегающие внешности. Пренебрежение
Ответ 9
для файла, содержащего данные, которые вы указали с именем foo.txt
python -c "print set(open('foo.txt').read())"
Ответ 10
Из оболочки это работает:
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
Это помещает символы в каждую строку в алфавитном порядке.
Ответ 11
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