Как удалить повторяющиеся символы и сохранить уникальный только в 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