¿Eliminar caracteres excepto dígitos de la cadena usando Python?

137

¿Cómo puedo eliminar todos los caracteres excepto los números de la cadena?

Jan Tojnar
fuente
@ Jan Tojnar: ¿Puedes dar un ejemplo?
João Silva
@JG: Tengo gtk.Entry () y quiero que se ingrese flotante múltiple.
Jan Tojnar
1
@JanTojnar utiliza el método re.sub según la respuesta dos y enumera explícitamente qué caracteres mantener, por ejemplo, re.sub ("[^ 0123456789 \.]", "", "Poo123.4and5fish")
Roger Heathcote

Respuestas:

112

En Python 2. *, con mucho, el enfoque más rápido es el .translatemétodo:

>>> x='aaa12333bb445bb54b5b52'
>>> import string
>>> all=string.maketrans('','')
>>> nodigs=all.translate(all, string.digits)
>>> x.translate(all, nodigs)
'1233344554552'
>>> 

string.maketranscrea una tabla de traducción (una cadena de longitud 256) que en este caso es la misma que ''.join(chr(x) for x in range(256))(simplemente más rápida de hacer ;-). .translateaplica la tabla de traducción (que aquí es irrelevante ya queall esencialmente significa identidad) Y elimina los caracteres presentes en el segundo argumento, la parte clave.

.translatefunciona de manera muy diferente en cadenas Unicode (y cadenas en Python 3 - lo hago preguntas deseos especifican los cuales importante de liberación de Python es de interés!) - no es este sencillo, no es tan rápido, aunque todavía bastante usable.

Volviendo a 2. *, la diferencia de rendimiento es impresionante ...:

$ python -mtimeit -s'import string; all=string.maketrans("", ""); nodig=all.translate(all, string.digits); x="aaa12333bb445bb54b5b52"' 'x.translate(all, nodig)'
1000000 loops, best of 3: 1.04 usec per loop
$ python -mtimeit -s'import re;  x="aaa12333bb445bb54b5b52"' 're.sub(r"\D", "", x)'
100000 loops, best of 3: 7.9 usec per loop

Acelerar las cosas entre 7 y 8 veces no es nada fácil, por lo translateque vale la pena conocer y utilizar el método. El otro enfoque popular no RE ...:

$ python -mtimeit -s'x="aaa12333bb445bb54b5b52"' '"".join(i for i in x if i.isdigit())'
100000 loops, best of 3: 11.5 usec per loop

es 50% más lento que RE, por lo que el .translateenfoque lo supera en un orden de magnitud.

En Python 3, o para Unicode, debe pasar .translateuna asignación (con ordinales, no caracteres directamente, como teclas) que devuelve Nonelo que desea eliminar. Aquí hay una manera conveniente de expresar esto para eliminar "todo menos" algunos caracteres:

import string

class Del:
  def __init__(self, keep=string.digits):
    self.comp = dict((ord(c),c) for c in keep)
  def __getitem__(self, k):
    return self.comp.get(k)

DD = Del()

x='aaa12333bb445bb54b5b52'
x.translate(DD)

También emite '1233344554552'. Sin embargo, poniendo esto en xx.py tenemos ...:

$ python3.1 -mtimeit -s'import re;  x="aaa12333bb445bb54b5b52"' 're.sub(r"\D", "", x)'
100000 loops, best of 3: 8.43 usec per loop
$ python3.1 -mtimeit -s'import xx; x="aaa12333bb445bb54b5b52"' 'x.translate(xx.DD)'
10000 loops, best of 3: 24.3 usec per loop

... que muestra que la ventaja de rendimiento desaparece, para este tipo de tareas de "eliminación", y se convierte en una disminución del rendimiento.

Alex Martelli
fuente
1
@sunqiang, sí, absolutamente, hay una razón por la que Py3k ha ido a Unicode como EL tipo de cadena de texto, en lugar de cadenas de bytes como en Py2, la misma razón por la que Java y C # siempre han tenido el mismo meme "cadena significa unicode" ... algunos gastos generales, tal vez, ¡pero MUCHO mejor soporte para casi cualquier cosa menos inglés! -).
Alex Martelli
29
x.translate(None, string.digits)en realidad da como resultado 'aaabbbbbb', que es lo contrario de lo que se pretende.
Tom Dalling
44
Haciendo eco de los comentarios de Tom Dalling, su primer ejemplo conserva todos los caracteres indeseables: hace lo contrario de lo que dijo.
Chris Johnson
3
@ RyanB.Lynch et al, la falla fue con un editor posterior y otros dos usuarios que aprobaron dicha edición , lo que, de hecho, es totalmente incorrecto. Revertido
Nick T
1
anulando la allconstrucción ... no estoy seguro de eso!
Andy Hayden
197

Uso re.sub, así:

>>> import re
>>> re.sub('\D', '', 'aas30dsa20')
'3020'

\D coincide con cualquier carácter que no sea un dígito, por lo tanto, el código anterior reemplaza esencialmente cada carácter que no sea un dígito para la cadena vacía.

O puede usar filter, así (en Python 2):

>>> filter(str.isdigit, 'aas30dsa20')
'3020'

Dado que en Python 3, filterdevuelve un iterador en lugar de a list, puede usar lo siguiente en su lugar:

>>> ''.join(filter(str.isdigit, 'aas30dsa20'))
'3020'
João Silva
fuente
Es malo en una tarea tan simple, la segunda es la mejor, creo, porque los métodos 'es ...' son los más rápidos para las cadenas.
f0b0s
su ejemplo de filtro está limitado a py2k
SilentGhost
2
@ f0b0s-iu9-info: ¿lo cronometraste? en mi máquina (py3k) RE es el doble de rápido que el filtro con isdigit, generador con isdigtestá a medio camino entre ellos
SilentGhost
@SilentGhost: Gracias, estaba usando IDLE de py2k. Ya está arreglado.
João Silva
1
@asmaier Simplemente use rpara cadena cruda:re.sub(r"\D+", "", "aas30dsa20")
Mitch McMabers
64
s=''.join(i for i in s if i.isdigit())

Otra variante de generador.

f0b0s
fuente
Lo mató ... + 1
Hubiera
17

Puedes usar filter:

filter(lambda x: x.isdigit(), "dasdasd2313dsa")

En python3.0 tienes que unirte a esto (un poco feo :()

''.join(filter(lambda x: x.isdigit(), "dasdasd2313dsa"))
freiksenet
fuente
solo en py2k, en py3k devuelve un generador
SilentGhost
convertir strpara listasegurarse de que funciona tanto en py2 como en py3:''.join(filter(lambda x: x.isdigit(), list("dasdasd2313dsa")))
Luiz C.
13

en la línea de la respuesta de bayer:

''.join(i for i in s if i.isdigit())
SilentGhost
fuente
No, esto no funcionará para números negativos porque -no es un dígito.
Oli
12

Puedes hacerlo fácilmente usando Regex

>>> import re
>>> re.sub("\D","","£70,000")
70000
Aminah Nuraini
fuente
De lejos, la forma más fácil
Iorek
55
¿En qué se diferencia esto de la respuesta de João Silva, que se proporcionó 7 años antes?
jww
7
x.translate(None, string.digits)

eliminará todos los dígitos de la cadena. Para eliminar letras y conservar los dígitos, haga esto:

x.translate(None, string.letters)
Terje Molnes
fuente
3
Obtengo un TypeError: translate () toma exactamente un argumento (2 dados). Por qué esta pregunta fue votada en su estado actual es bastante frustrante.
Bobort
la traducción cambió de python 2 a 3. La sintaxis que usa este método en python 3 es x.translate (str.maketrans ('', '', string.digits)) y x.translate (str.maketrans ('', '' , string.ascii_letters)). Ninguna de estas tiras de espacio en blanco. Realmente ya no recomendaría este enfoque ...
ZaxR
5

La opción menciona en los comentarios que quiere mantener el lugar decimal. Esto se puede hacer con el método re.sub (según el segundo y la mejor respuesta de la OMI) enumerando explícitamente los caracteres para mantener, por ejemplo

>>> re.sub("[^0123456789\.]","","poo123.4and5fish")
'123.45'
Roger Heathcote
fuente
¿Qué pasa con "poo123.4and.5fish"?
Jan Tojnar
En mi código verifico el número de períodos en la cadena de entrada y provoco un error si es más de 1.
Roger Heathcote
4

Una versión rápida para Python 3:

# xx3.py
from collections import defaultdict
import string
_NoneType = type(None)

def keeper(keep):
    table = defaultdict(_NoneType)
    table.update({ord(c): c for c in keep})
    return table

digit_keeper = keeper(string.digits)

Aquí hay una comparación de rendimiento vs. regex:

$ python3.3 -mtimeit -s'import xx3; x="aaa12333bb445bb54b5b52"' 'x.translate(xx3.digit_keeper)'
1000000 loops, best of 3: 1.02 usec per loop
$ python3.3 -mtimeit -s'import re; r = re.compile(r"\D"); x="aaa12333bb445bb54b5b52"' 'r.sub("", x)'
100000 loops, best of 3: 3.43 usec per loop

Entonces, es un poco más de 3 veces más rápido que la expresión regular, para mí. También es más rápido que el class Delanterior, porque defaultdictrealiza todas sus búsquedas en C, en lugar de Python (lento). Aquí está esa versión en mi mismo sistema, para comparar.

$ python3.3 -mtimeit -s'import xx; x="aaa12333bb445bb54b5b52"' 'x.translate(xx.DD)'
100000 loops, best of 3: 13.6 usec per loop
rescdsk
fuente
3

Use una expresión generadora:

>>> s = "foo200bar"
>>> new_s = "".join(i for i in s if i in "0123456789")
bayer
fuente
en lugar de hacer''.join(n for n in foo if n.isdigit())
shxfee
2

Feo pero funciona:

>>> s
'aaa12333bb445bb54b5b52'
>>> a = ''.join(filter(lambda x : x.isdigit(), s))
>>> a
'1233344554552'
>>>
Gant
fuente
¿por qué haces list(s)?
SilentGhost
@SilentGhost es mi malentendido. lo había corregido gracias :)
Gant
En realidad, con este método, no creo que necesite usar "unirse". filter(lambda x: x.isdigit(), s)funcionó bien para mí ... oh, es porque estoy usando Python 2.7.
Bobort
1
$ python -mtimeit -s'import re;  x="aaa12333bb445bb54b5b52"' 're.sub(r"\D", "", x)'

100000 bucles, lo mejor de 3: 2,48 usec por bucle

$ python -mtimeit -s'import re; x="aaa12333bab445bb54b5b52"' '"".join(re.findall("[a-z]+",x))'

100000 bucles, lo mejor de 3: 2.02 usec por bucle

$ python -mtimeit -s'import re;  x="aaa12333bb445bb54b5b52"' 're.sub(r"\D", "", x)'

100000 bucles, lo mejor de 3: 2.37 usec por bucle

$ python -mtimeit -s'import re; x="aaa12333bab445bb54b5b52"' '"".join(re.findall("[a-z]+",x))'

100000 bucles, lo mejor de 3: 1.97 usec por bucle

Había observado que join es más rápido que sub.

AnilReddy
fuente
¿Por qué repites los dos métodos dos veces? ¿Y podría describir cómo su respuesta es diferente de la aceptada?
Jan Tojnar
Ambas dan como resultado la misma salida. Pero, solo quiero mostrar que unirse es más rápido que el método secundario en los resultados.
AnilReddy
No lo hacen, su código hace lo contrario. Y también tienes cuatro medidas pero solo dos métodos.
Jan Tojnar
1

Puedes leer cada personaje. Si es un dígito, inclúyalo en la respuesta. El str.isdigit() método es una forma de saber si un personaje es un dígito.

your_input = '12kjkh2nnk34l34'
your_output = ''.join(c for c in your_input if c.isdigit())
print(your_output) # '1223434'
yoelvis
fuente
¿Cómo es esto diferente de la respuesta de f0b0s? Habrá que editar esa respuesta en su lugar si usted tiene más información para llevar
chevybow
0

No es un trazador de líneas pero muy simple:

buffer = ""
some_str = "aas30dsa20"

for char in some_str:
    if not char.isdigit():
        buffer += char

print( buffer )
Josh
fuente
0

Yo usé esto 'letters'debe contener todas las letras de las que desea deshacerse:

Output = Input.translate({ord(i): None for i in 'letters'}))

Ejemplo:

Input = "I would like 20 dollars for that suit" Output = Input.translate({ord(i): None for i in 'abcdefghijklmnopqrstuvwxzy'})) print(Output)

Salida: 20

Gustav
fuente