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