Как удалить повторяющиеся символы и сохранить уникальный только в Perl? - PullRequest
11 голосов
/ 06 апреля 2010

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

EFUAHUU
UUUEUUUUH
UJUJHHACDEFUCU

Ожидаемый результат:

EFUAH
UEH
UJHACDEF

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

Ответы [ 11 ]

15 голосов
/ 06 апреля 2010

Это можно сделать, используя положительный прогноз :

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

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

  • .: для сопоставления с любым символом.
  • первый (): помните, что совпадает одиночный символ.
  • (?=...): + ve lookahead
  • .*?: чтобы соответствовать чему-либо между
  • \1: запомненный матч.
  • (.)(?=.*?\1): сопоставьте и запомните любой символ только если появляется снова позже в строке.
  • s///: Perl способ сделать замена.
  • g: сделать замену глобально ... это не останавливаться после первая замена.
  • s/(.)(?=.*?\1)//g: это будет удалить символ из входной строки только если этот символ появится снова позже в строке.

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

Чтобы сохранить относительный порядок без изменений, мы можем сделать то, что KennyTM говорит в одном из комментариев:

  • перевернуть строку ввода
  • сделать замену, как и раньше
  • поменять результат перед печатью

Одна строка Perl для этого:

* * 1068

Поскольку мы делаем print вручную после обращения, мы не используем флаг -p, но используем флаг -n.

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

5 голосов
/ 07 апреля 2010

если 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 голосов
/ 06 апреля 2010

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

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

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

4 голосов
/ 06 апреля 2010
perl -ne'my%s;print grep!$s{$_}++,split//'
1 голос
/ 06 апреля 2010

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

Мне плохо - он удаляет только прилегающие появления. Игнорировать

1 голос
/ 06 апреля 2010

Использовать uniq из Список :: MoreUtils :

perl -MList::MoreUtils=uniq -ne 'print uniq split ""'
1 голос
/ 06 апреля 2010

Это похоже на классическое приложение позитивного взгляда, но, к сожалению, Perl не поддерживает это.Фактически, сделать это (сопоставив предыдущий текст символа в строке с полным регулярным выражением, длина которого не определима), я думаю, можно сделать только с классами регулярных выражений .NET.

Однако положительный прогноз поддерживает полныйрегулярные выражения, так что все, что вам нужно сделать, это перевернуть строку, применить положительный прогноз (как сказал unicornaddict):

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

И повернуть его обратно, потому что без обратного, который будет только дублировать символ наПоследнее место в строке.

MASSIVE EDIT

Я потратил последние полчаса на это, и похоже, что это работает, безреверсивный .

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

Я не знаю, гордиться или ужасаться.Я в основном делаю положительную петлю, затем подставляю строку с указанным \ G - что заставляет механизм регулярных выражений начинать сопоставление с последнего совпадения (внутренне представленного переменной pos ()).

Стестовый ввод, такой как:

aabbbcbbccbabb

EFAUUUUH

ABCBBBBD

DEEEFEGGH

AABBCC

Выход выглядит следующим образом:

abc

EFAUH

ABCD

DEFGH

ABC

Я думаю это работает ...

Объяснение - Хорошо, если мое объяснение в прошлый раз не было достаточно ясным - взгляд в будущееи остановиться на последнем совпадении дубликатов переменной [в коде вы можете сделать вывод pos ();внутри цикла для проверки] и s / \ G // g удалит его [вам действительно не нужен / g].Таким образом, в цикле подстановка будет продолжаться, пока все такие дубликаты не будут удалены.Конечно, это может быть немного слишком интенсивно для вашего вкуса ... но вы также увидите большинство решений на основе регулярных выражений.Однако метод реверсирования / предпросмотра, вероятно, будет более эффективным, чем этот.

1 голос
/ 06 апреля 2010

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();
0 голосов
/ 06 апреля 2010
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 голосов
/ 06 апреля 2010

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

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

Это помещает символы в каждой строке в алфавитном порядке.

...