2010-04-06 18 views

Respuesta

15

Esto se puede hacer usando positive lookahead:

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

La expresión regular utilizada es: (.)(?=.*?\1)

  • .: para combinar con cualquier carbón.
  • primer (): recuerde el emparejado solo char.
  • (?=...): + ve lookahead
  • .*?: para que coincida con cualquier otra cosa
  • \1: el partido recordado.
  • (.)(?=.*?\1): igualar y recordar cualquier carbón sólo si aparece de nuevo más adelante en la cadena.
  • s///: Perl forma de hacer la sustitución .
  • g: hacer la sustitución en todo el mundo ... eso no se detiene después de primera sustitución.
  • s/(.)(?=.*?\1)//g: esto hará eliminar un carácter de la cadena de entrada solo si ese carácter aparece de nuevo más tarde en la cadena.

Esto le no mantener el orden del caracter en la entrada porque por cada Char único en la cadena de entrada, conservamos su ocurrencia última y no primera.

Para mantener el orden relativo intacta podemos hacer lo KennyTM dice en uno de los comentarios:

  • invertir la línea de entrada
  • hacer la sustitución como antes
  • revertir el resultado antes de imprimir

El Perl una línea para este es:

perl -ne '$_=reverse;s/(.)(?=.*?\1)//g;print scalar reverse;' FILE_NAME 

Dado que estamos haciendo print manualmente después de la inversión, no usamos el indicador -p, pero usamos el indicador -n.

No estoy seguro de si este es el mejor delineador para hacer esto. Invito a otros a editar esta respuesta si tienen una mejor alternativa.

+2

El orden ha cambiado (por ejemplo, "EFAHU") - me pregunto si es importante. –

+0

@Gavin: eso se puede solucionar invirtiendo la cadena inicialmente, e invertir la cadena después del reemplazo. – kennytm

+2

Bueno, esto es increíble !!!! ¿Pero puede explicarme detalles de bit como qué ===> s/(.) Y (? =. *? \ 1) // está haciendo? También es posible tener en el mismo orden que he puesto en mi consulta inicial, Por ej. actualmente recibo EFAHU en lugar de EFUAH, que es más útil. Thnax a ton :) – manu

0

para un archivo que contiene los datos que lista llamada foo.txt

python -c "print set(open('foo.txt').read())" 
+2

conjuntos en Python no tiene orden ... y él quiere Perl .. – ghostdog74

+0

Su publicación original no especificó Perl como un requisito (aunque lo etiquetó perl), solo señaló que encontró una perl -liner como una posible forma de hacerlo. Tampoco dijo que el orden importara, solo la singularidad. Además, el uso de un trazador indica que el método realmente no importa. – jkyle

1

Tie :: IxHash es un buen módulo para almacenar el fin de hash (pero puede ser lento, que se necesitan para referencia si la velocidad es importante) Ejemplo con pruebas:

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(); 
3
perl -ne'my%s;print grep!$s{$_}++,split//' 
+0

Esto también está funcionando y es más corto que el anterior. Estoy abrumado por la respuesta :) Me gustaría saber si funciona si es posible. – manu

+0

Funciona de la misma manera que la solución gianthare pero mucho más idiomática Perl y más rápida. –

+0

Bueno, estoy de acuerdo. Casi un trazador de líneas a excepción del 'my% s'. Aunque no veo de dónde viene la aceleración. ¿Puede ser de hashtable nuevo en lugar de restablecer? ¿O es grep más eficiente que el bucle explícito? –

0

Desde el shell, esto funciona:

sed -e 's/$/<EOL>/ ; s/./&\n/g' test.txt | uniq | sed -e :a -e '$!N; s/\n//; ta ; s/<EOL>/\n/g' 

En palabras: marcar cada salto de línea con una cadena <EOL>, a continuación, poner todos los caracteres en una línea propia, luego use uniq para eliminar las líneas duplicadas, luego quite todos los saltos de línea, luego coloque los saltos de línea en lugar de los marcadores <EOL>.

He encontrado la parte -e :a -e '$!N; s/\n//; ta en un mensaje en el foro y no entiendo la -e :a parte separada, o la parte $!N, así que si alguien puede explicar aquellos, estaría agradecido.

Hmm, ese solo hace duplicados consecutivos; para eliminar todos los duplicados que podría hacer esto:

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 

que pone los caracteres en cada línea en orden alfabético sin embargo.

1

Esto se ve como una aplicación clásica de lookbehind positivo, pero lamentablemente Perl no es compatible con eso. De hecho, hacer esto (coincidir con el texto anterior de un personaje en una cadena con una expresión regular completa cuya longitud es indeterminable) solo se puede hacer con clases .NET regex, creo.

Sin embargo, de búsqueda positiva hacia soporta expresiones regulares completos, así que todo lo que necesita hacer es revertir la cadena, se aplican búsqueda positiva hacia delante (como unicornaddict dijo):

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

y revertir de nuevo, ya que sin la inversa que' Solo mantendré el personaje duplicado en el último lugar de una línea.

EDITAR MASIVO

He pasado la última media hora en esto, y esto parece que esto funciona, sin la marcha atrás.

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

No sé si estar orgulloso u horrorizado. Básicamente estoy haciendo looakahead positivo, y luego sustituyendo en la cadena con \ G especificado, lo que hace que el motor regex comience su coincidencia desde el último lugar coincidente (representado internamente por la variable pos()).

Con la entrada de prueba como esta:

aabbbcbbccbabb

EFAUUUUH

ABCBBBBD

DEEEFEGGH

AABBCC

La salida es la siguiente:

abc

EFAUH

ABCD

DEFGH

ABC

I pensar está funcionando ...

Explicación - Está bien, en caso de que mi explicación última vez no era lo suficientemente claro - la búsqueda hacia delante irá y parar en el último partido de una variable duplicado [en el código se puede hacer una imprimir pos(); dentro del ciclo para verificar] y s/\ G // g lo eliminará [no necesitas realmente el/g]. Entonces, dentro del ciclo, la sustitución continuará eliminándose hasta que se eliminen todos los duplicados. Por supuesto, esto puede ser un poco demasiado intensivo en el uso del procesador para su gusto ... pero también lo son la mayoría de las soluciones basadas en expresiones regulares que verá. Sin embargo, el método de reversión/anticipación probablemente sea más eficiente que esto.

+2

Más precisamente, es * look-behind de longitud variable * Perl no es compatible. Además de .NET, son compatibles con JGSoft (EditPad Pro, PowerGrep) y en forma más limitada con Java. –

+0

Se editó y se agregó una nueva solución. No estoy seguro si es a prueba de todo o no ... demasiada cafeína. :-P –

1

Si el conjunto de caracteres que se pueden encontrar está restringido, por ejemplo, sólo letras, a continuación, la solución más fácil será con tr
perl -p -e 'tr/a-zA-Z/a-zA-Z/s'
Reemplazará todas las letras por sí mismos, dejando otros caracteres no afectado y el modificador/s se apriete veces que se repite el mismo carácter (después de la sustitución), por lo tanto la eliminación de duplicados

Malo: solo elimina las apariencias adyacentes.Ignorar

4

Aquí hay una solución, que creo que debería funcionar más rápido que la del futuro, pero no está basada en expresiones regulares y usa hashtable.

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

se divide cada línea en personajes y se imprime sólo la primera aparición contando apariciones dentro% visto tabla hash

0
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 

La salida:

EFUAH 
UEH 
UJHACDEF 
4

si Perl no es una necesidad, también puedes usar awk. Aquí hay un punto de referencia divertido sobre los anuncios de Perl one publicados contra awk. awk es 10+ segundos más rápido para un archivo con 3million ++ líneas

$ wc -l <file2 
220 

$ 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 
+0

+1, buen trabajo :) – codaddict

+0

Estoy sorprendido de lo rápido que es la solución de expresiones regulares –

Cuestiones relacionadas