Remover caracteres, exceto dígitos da string, usando Python?

137

Como posso remover todos os caracteres, exceto números da string?

Jan Tojnar
fonte
@ Jan Tojnar: Você pode dar um exemplo?
João Silva
@JG: Eu tenho gtk.Entry () e quero multiplicar float inserido nele.
Jan Tojnar
1
@JanTojnar usa o método re.sub conforme a resposta dois e lista explicitamente quais caracteres devem ser mantidos, por exemplo, re.sub ("[^ 0123456789 \.]", "", "Poo123.4and5fish")
Roger Heathcote -

Respostas:

112

No Python 2. *, de longe a abordagem mais rápida é o .translatemétodo:

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

string.maketranscria uma tabela de conversão (uma string de comprimento 256) que, neste caso, é a mesma que ''.join(chr(x) for x in range(256))(apenas mais rápida de fazer ;-). .translateaplica a tabela de conversão (que aqui é irrelevante, pois allsignifica essencialmente identidade) E exclui os caracteres presentes no segundo argumento - a parte principal.

.translatefunciona de forma muito diferente em cadeias de caracteres Unicode (e strings em Python 3 - I fazer perguntas desejo especificado que grande-lançamento do Python é de interesse!) - não é tão simples, não é bem isso rápido, embora ainda bastante utilizável.

De volta a 2. *, a diferença de desempenho é impressionante ...:

$ 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 as coisas em 7-8 vezes dificilmente é um amendoim, portanto translatevale a pena conhecer e usar o método. A outra abordagem popular não-ER ...:

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

é 50% mais lento que ER, então a .translateabordagem supera-a em uma ordem de magnitude.

No Python 3 ou no Unicode, você precisa passar .translateum mapeamento (com ordinais, não caracteres diretamente, como chaves) que retorna Nonepara o que você deseja excluir. Aqui está uma maneira conveniente de expressar isso para excluir "tudo menos" alguns 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)

também emite '1233344554552'. No entanto, colocando isso no xx.py, temos ...:

$ 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 mostra que a vantagem de desempenho desaparece para esse tipo de "exclusão" de tarefas e se torna uma diminuição de desempenho.

Alex Martelli
fonte
1
@sunqiang, sim, absolutamente - há uma razão pela qual o Py3k foi para o Unicode como o tipo de string de texto, em vez de por bytes como no Py2 - pela mesma razão que Java e C # sempre tiveram o mesmo meme "string significa unicode" ... algumas despesas gerais, talvez, mas MUITO melhor suporte para praticamente qualquer coisa, menos inglês! -).
Alex Martelli
29
x.translate(None, string.digits)na verdade resulta em 'aaabbbbbb', que é o oposto do que se destina.
26612 Tom Dalling
4
Ecoando os comentários de Tom Dalling, seu primeiro exemplo mantém todos os personagens indesejáveis ​​- faz o oposto do que você disse.
Chris Johnson
3
@ RyanB.Lynch et al., A culpa foi de um editor posterior e de outros dois usuários que aprovaram a edição , que, de fato, está totalmente errada. Revertido.
Nick T
1
substituindo allbuiltin ... não tenho certeza sobre isso!
Andy Hayden
197

Use re.sub, assim:

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

\D corresponde a qualquer caractere que não seja um dígito, portanto, o código acima substitui essencialmente todos os caracteres que não são dígitos da string vazia.

Ou você pode usar filter, assim (no Python 2):

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

Como no Python 3, filterretorna um iterador em vez de a list, você pode usar o seguinte:

>>> ''.join(filter(str.isdigit, 'aas30dsa20'))
'3020'
João Silva
fonte
re é mau em uma tarefa tão simples, a segunda é a melhor que penso, porque os métodos 'is ...' são os mais rápidos para as strings.
F0b0s
seu exemplo de filtro é limitado a py2k
SilentGhost 20/09/2009
2
@ f0b0s-iu9-info: você cronometrou? na minha máquina (Py3k) Re é duas vezes mais rápido do que com filtro isdigit, com gerador isdigtestá a meio caminho entre eles
SilentGhost
@ SilentGhost: Obrigado, eu estava usando o IDLE do py2k. Está consertado agora.
João Silva
1
@asmaier Simplesmente use rpara string bruta:re.sub(r"\D+", "", "aas30dsa20")
Mitch McMabers 6/11/19
64
s=''.join(i for i in s if i.isdigit())

Outra variante do gerador.

f0b0s
fonte
Matou .. + 1 teria sido ainda melhor se foi usado lambda
Barath Ravikumar
17

Você pode usar o filtro:

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

No python3.0 você tem que se juntar a isso (meio feio :()

''.join(filter(lambda x: x.isdigit(), "dasdasd2313dsa"))
freiksenet
fonte
apenas em py2k, em Py3k ele retorna um gerador
SilentGhost
converta strpara listgarantir que ele funcione em py2 e py3:''.join(filter(lambda x: x.isdigit(), list("dasdasd2313dsa")))
Luiz C.
13

na linha da resposta da bayer:

''.join(i for i in s if i.isdigit())
SilentGhost
fonte
Não, isso não funcionará para números negativos porque -não é um dígito.
Oli
12

Você pode fazer isso facilmente usando o Regex

>>> import re
>>> re.sub("\D","","£70,000")
70000
Aminah Nuraini
fonte
De longe a maneira mais fácil
Iorek
5
Como isso é diferente da resposta de João Silva, fornecida sete anos antes?
JWW
7
x.translate(None, string.digits)

excluirá todos os dígitos da string. Para excluir letras e manter os dígitos, faça o seguinte:

x.translate(None, string.letters)
Terje Molnes
fonte
3
Recebo um TypeError: translate () leva exatamente um argumento (2 dados). Por que essa pergunta foi votada em seu estado atual é bastante frustrante.
Bobort
conversão alterada do python 2 para 3. A sintaxe que usa esse método no python 3 é x.translate (str.maketrans ('', '', string.digits)) e x.translate (str.maketrans ('', '' , string.ascii_letters)). Nenhuma dessas faixas de espaço em branco. Eu realmente não recomendo essa abordagem mais ...
ZaxR
5

A opção menciona nos comentários que ele deseja manter a casa decimal. Isso pode ser feito com o método re.sub (conforme a segunda e melhor resposta do IMHO) listando explicitamente os caracteres a serem mantidos, por exemplo

>>> re.sub("[^0123456789\.]","","poo123.4and5fish")
'123.45'
Roger Heathcote
fonte
E quanto a "poo123.4and.5fish"?
Jan Tojnar
No meu código eu verificar o número de períodos na cadeia de entrada e gerar um erro se isso é mais do que 1.
Roger Heathcote
4

Uma versão 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)

Aqui está uma comparação de desempenho versus 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

Então, é um pouco mais de três vezes mais rápido que o regex, para mim. Também é mais rápido do que o class Delanterior, porque defaultdictfaz todas as suas pesquisas em C, em vez de (lento) Python. Aqui está essa versão no meu mesmo sistema, para comparação.

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

Use uma expressão geradora:

>>> s = "foo200bar"
>>> new_s = "".join(i for i in s if i in "0123456789")
bayer
fonte
em vez fazer''.join(n for n in foo if n.isdigit())
shxfee
2

Feio, mas funciona:

>>> s
'aaa12333bb445bb54b5b52'
>>> a = ''.join(filter(lambda x : x.isdigit(), s))
>>> a
'1233344554552'
>>>
Gant
fonte
por que você faz list(s)?
SilentGhost 20/09/09
@SilentGhost é o meu mal-entendido. tinha corrigido graças :)
Gant
Na verdade, com esse método, não acho que você precise usar "join". filter(lambda x: x.isdigit(), s)funcionou bem para mim. ... ah, é porque eu estou usando o Python 2.7.
Bobort
1
$ python -mtimeit -s'import re;  x="aaa12333bb445bb54b5b52"' 're.sub(r"\D", "", x)'

100000 loops, o melhor de 3: 2,48 usec por loop

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

100000 loops, o melhor de 3: 2,02 usec por loop

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

100000 loops, o melhor de 3: 2,37 usec por loop

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

100000 loops, o melhor de 3: 1,97 usec por loop

Eu tinha observado que a junção é mais rápida que a sub.

AnilReddy
fonte
Por que você está repetindo os dois métodos duas vezes? E você poderia descrever como sua resposta é diferente da resposta aceita?
Jan Tojnar
Ambos resultam na mesma saída. Mas eu só quero mostrar que a junção é mais rápida do sub método nos resultados.
AnilReddy 17/07/19
Eles não, seu código faz o oposto. E você também tem quatro medidas, mas apenas dois métodos.
Jan Tojnar
1

Você pode ler cada personagem. Se for dígito, inclua-o na resposta. O str.isdigit() método é uma maneira de saber se um caractere é um dígito.

your_input = '12kjkh2nnk34l34'
your_output = ''.join(c for c in your_input if c.isdigit())
print(your_output) # '1223434'
yoelvis
fonte
como isso é diferente da resposta de f0b0s? Você deve editar essa resposta em vez se você tiver mais informações para trazer
chevybow
0

Não é um liner, mas muito simples:

buffer = ""
some_str = "aas30dsa20"

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

print( buffer )
Josh
fonte
0

Eu usei isso. 'letters'deve conter todas as letras das quais você deseja se livrar:

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

Exemplo:

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

Resultado: 20

Gustav
fonte