Как я удаляю дублирующиеся символы и сохраняю уникальный только в Perl?

Как я удаляю дублирующиеся символы и сохраняю уникальный только. Например, мой вход:

EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU

Ожидаемый вывод:

EFUAH
UEH
UJHACDEF

Я столкнулся perl -pe's/$1//g while/(.).*\/' который замечателен, но это удаляет даже отдельное проявление символа в выводе.

11
задан codaddict 5 February 2011 в 04:02
поделиться

11 ответов

Это можно сделать с помощью положительного просмотра вперед :

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

Используемое регулярное выражение: (.) (? =. *? \ 1)

  • . : для соответствия любому символу.
  • first () : запомните совпавший одиночный символ .
  • (? = ...) : + 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 флаг.

Я не уверен, что это лучший однострочный вариант для этого. Я приветствую других редактировать этот ответ, если у них есть лучшая альтернатива.

15
ответ дан 3 December 2019 в 02:30
поделиться
perl -ne'my%s;print grep!$s{$_}++,split//'
4
ответ дан 3 December 2019 в 02:30
поделиться

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();
1
ответ дан 3 December 2019 в 02:30
поделиться

Это похоже на классическое применение положительного просмотра назад, но, к сожалению, 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]. Таким образом, внутри цикла подстановка будет продолжать удаление до тех пор, пока не будут удалены все такие дубликаты. Конечно, на ваш вкус может потребоваться слишком много ресурсов процессора ... но то же самое и с большинством решений на основе регулярных выражений, которые вы увидите. Однако метод реверсирования / просмотра вперед, вероятно, будет более эффективным, чем этот.

1
ответ дан 3 December 2019 в 02:30
поделиться

Если набор символов, которые можно встретить, ограничен, например только буквы, тогда самым простым решением будет tr
perl -p -e 'tr / a-zA-Z / a-zA-Z / s'
Он заменит все буквы на себя, оставив другие не затрагиваются символы и модификатор / s сжимает повторяющиеся вхождения одного и того же символа (после замены), таким образом удаляя дубликаты.

Мне плохо - он удаляет только смежные появления. Игнорировать

1
ответ дан 3 December 2019 в 02:30
поделиться
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
0
ответ дан 3 December 2019 в 02:30
поделиться

Из оболочки это работает:

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

Тем не менее, при этом символы в каждой строке будут располагаться в алфавитном порядке.

0
ответ дан 3 December 2019 в 02:30
поделиться

для файла, содержащего перечисленные вами данные, с именем foo.txt

python -c "print set(open('foo.txt').read())"
0
ответ дан 3 December 2019 в 02:30
поделиться

если 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
5
ответ дан 3 December 2019 в 02:30
поделиться

Вот решение, которое, как мне кажется, должно работать быстрее, чем прогнозируемое, но оно не основано на регулярных выражениях и использует хеш-таблицу.

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

Каждая строка разбивается на символы и печатается только первое появление путем подсчета появлений внутри хэш-таблицы% visible

4
ответ дан 3 December 2019 в 02:30
поделиться

Используйте uniq из List :: MoreUtils :

perl -MList::MoreUtils=uniq -ne 'print uniq split ""'
1
ответ дан 3 December 2019 в 02:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: