Aquí podría ser tu PUBLICIDAD


¿Cuál es la forma más eficiente en Python para convertir una cadena en minúscula eliminando todos los caracteres alfa no alfa?

votos
27

Tengo una tarea simple que debo realizar en Python, que consiste en convertir una cadena en minúsculas y quitar todos los caracteres que no son alfa de Asci.

Por ejemplo:

This is a Test -> thisisatest
A235th@#$&( er Ra{}|?>ndom -> atherrandom

Tengo una función simple para hacer esto:

import string
import sys

def strip_string_to_lowercase(s):
    tmpStr = s.lower().strip()
    retStrList = []
    for x in tmpStr:
        if x in string.ascii_lowercase:
            retStrList.append(x)

    return ''.join(retStrList)

Pero no puedo evitar pensar que hay una forma más eficiente o más elegante.

¡Gracias!


Editar:

Gracias a todos los que respondieron. Aprendí, y en algunos casos volví a aprender, una buena cantidad de pitón.

Publicado el 12/03/2009 a las 15:35
fuente por usuario grieve
En otros idiomas...        العربية       

11 respuestas

votos
24

Otra solución (no tan pitonica, pero muy rápida) es usar string.translate, aunque ten en cuenta que esto no funcionará para unicode. También vale la pena señalar que puede acelerar el código de Dana moviendo los caracteres en un conjunto (que busca el hash, en lugar de realizar una búsqueda lineal cada vez). Aquí están los tiempos que obtengo para varias de las soluciones dadas:

import string, re, timeit

# Precomputed values (for str_join_set and translate)

letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
                       string.ascii_lowercase * 2)
deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)

s="A235th@#$&( er Ra{}|?>ndom"

# From unwind's filter approach
def test_filter(s):
    return filter(lambda x: x in string.ascii_lowercase, s.lower())

# using set instead (and contains)
def test_filter_set(s):
    return filter(letter_set.__contains__, s).lower()

# Tomalak's solution
def test_regex(s):
    return re.sub('[^a-z]', '', s.lower())

# Dana's
def test_str_join(s):
    return ''.join(c for c in s.lower() if c in string.ascii_lowercase)

# Modified to use a set.
def test_str_join_set(s):
    return ''.join(c for c in s.lower() if c in letter_set)

# Translate approach.
def test_translate(s):
    return string.translate(s, tab, deletions)


for test in sorted(globals()):
    if test.startswith("test_"):
        assert globals()[test](s)=='atherrandom'
        print "%30s : %s" % (test, timeit.Timer("f(s)", 
              "from __main__ import %s as f, s" % test).timeit(200000))

Esto me da:

               test_filter : 2.57138351271
           test_filter_set : 0.981806765698
                test_regex : 3.10069885233
             test_str_join : 2.87172979743
         test_str_join_set : 2.43197956381
            test_translate : 0.335367566218

[Editar] También se actualizó con soluciones de filtro. (Tenga en cuenta que el uso set.__contains__hace una gran diferencia aquí, ya que evita hacer una llamada de función adicional para el lambda.

Respondida el 12/03/2009 a las 05:06
fuente por usuario Brian


Aquí podría ser tu PUBLICIDAD


votos
17
>>> filter(str.isalpha, "This is a Test").lower()
'thisisatest'
>>> filter(str.isalpha, "A235th@#$&( er Ra{}|?>ndom").lower()
'atherrandom'
Respondida el 12/03/2009 a las 04:55
fuente por usuario A. Coady

votos
11

No especialmente eficiente en el tiempo de ejecución, pero ciertamente más agradable en los ojos del codificador, pobres y cansados:

def strip_string_and_lowercase(s):
    return ''.join(c for c in s.lower() if c in string.ascii_lowercase)
Respondida el 12/03/2009 a las 03:39
fuente por usuario Dana

votos
8

Me gustaría:

  • minúsculas de la cadena
  • reemplace todo [^a-z]con""

Como eso:

def strip_string_to_lowercase():
  nonascii = re.compile('[^a-z]')
  return lambda s: nonascii.sub('', s.lower().strip())

EDITAR: Resulta que la versión original (a continuación) es muy lenta, aunque se puede obtener algún rendimiento convirtiéndola en un cierre (arriba).

def strip_string_to_lowercase(s):
  return re.sub('[^a-z]', '', s.lower().strip())

Mis medidas de rendimiento con 100.000 iteraciones contra la cadena

"A235th@#$&( er Ra{}|?>ndom"

reveló que:

Por el bien de la prueba, no obtuve printlos resultados.

Respondida el 12/03/2009 a las 03:39
fuente por usuario Tomalak

votos
6

translateMétodo Python 2.x

Convierta a minúsculas y filtre caracteres no alpha no alfa según ASCII:

from string import ascii_letters, ascii_lowercase, maketrans

table = maketrans(ascii_letters, ascii_lowercase*2)
deletechars = ''.join(set(maketrans('','')) - set(ascii_letters))

print "A235th@#$&( er Ra{}|?>ndom".translate(table, deletechars)
# -> 'atherrandom'

translateMétodo Python 3

Filtrar no ascii:

ascii_bytes = "A235th@#$&(٠٫٢٥ er Ra{}|?>ndom".encode('ascii', 'ignore')

Use bytes.translate()para convertir a minúsculas y eliminar bytes no alfa:

from string import ascii_letters, ascii_lowercase

alpha, lower = [s.encode('ascii') for s in [ascii_letters, ascii_lowercase]]
table = bytes.maketrans(alpha, lower*2)           # convert to lowercase
deletebytes = bytes(set(range(256)) - set(alpha)) # delete nonalpha

print(ascii_bytes.translate(table, deletebytes))
# -> b'atherrandom'
Respondida el 12/03/2009 a las 06:56
fuente por usuario jfs

votos
4

Similar a la de @ Dana, pero creo que esto suena como un trabajo de filtrado, y eso debería ser visible en el código. También sin la necesidad de llamar explícitamente join():

def strip_string_to_lowercase(s):
  return filter(lambda x: x in string.ascii_lowercase, s.lower())
Respondida el 12/03/2009 a las 03:44
fuente por usuario unwind

votos
3

Agregué las soluciones de filtro al código de Brian:

import string, re, timeit

# Precomputed values (for str_join_set and translate)

letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
                       string.ascii_lowercase * 2)
deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)

s="A235th@#$&( er Ra{}|?>ndom"

def test_original(s):
    tmpStr = s.lower().strip()
    retStrList = []
    for x in tmpStr:
        if x in string.ascii_lowercase:
            retStrList.append(x)

    return ''.join(retStrList)


def test_regex(s):
    return re.sub('[^a-z]', '', s.lower())

def test_regex_closure(s):
  nonascii = re.compile('[^a-z]')
  def replacer(s):
    return nonascii.sub('', s.lower().strip())
  return replacer(s)


def test_str_join(s):
    return ''.join(c for c in s.lower() if c in string.ascii_lowercase)

def test_str_join_set(s):
    return ''.join(c for c in s.lower() if c in letter_set)

def test_filter_set(s):
    return filter(letter_set.__contains__, s.lower())

def test_filter_isalpha(s):
    return filter(str.isalpha, s).lower()

def test_filter_lambda(s):
    return filter(lambda x: x in string.ascii_lowercase, s.lower())

def test_translate(s):
    return string.translate(s, tab, deletions)

for test in sorted(globals()):
    if test.startswith("test_"):
        print "%30s : %s" % (test, timeit.Timer("f(s)", 
              "from __main__ import %s as f, s" % test).timeit(200000))

Esto me da:

       test_filter_isalpha : 1.31981746283
        test_filter_lambda : 2.23935583992
           test_filter_set : 0.76511679557
             test_original : 2.13079176264
                test_regex : 2.44295629752
        test_regex_closure : 2.65205913042
             test_str_join : 2.25571266739
         test_str_join_set : 1.75565888961
            test_translate : 0.269259640541

Parece que isalpha está usando un algoritmo similar, al menos en términos de O (), al algoritmo establecido.


Editar: se agregó el conjunto de filtros y se cambiaron las funciones del filtro para que fuera un poco más claro.

Respondida el 12/03/2009 a las 05:20
fuente por usuario grieve

votos
2

Esta es una aplicación típica de la lista de comprensión:

import string
s = "O235th@#$&( er Ra{}|?<ndom"
print ''.join(c for c in s.lower() if c in string.ascii_lowercase)

No filtrará "<" (entidad html), como en su ejemplo, pero asumo que fue un corte accidental y un problema pasado.

Respondida el 12/03/2009 a las 03:43
fuente por usuario Ber

votos
2
>>> import string
>>> a = "O235th@#$&( er Ra{}|?&lt;ndom"
>>> ''.join(i for i in a.lower() if i in string.ascii_lowercase)
'otheraltndom'

haciendo esencialmente lo mismo que tú.

Respondida el 12/03/2009 a las 03:42
fuente por usuario SilentGhost

votos
0

Python 2.x:

import string
valid_chars= string.ascii_lowercase + string.ascii_uppercase

def only_lower_ascii_alpha(text):
    return filter(valid_chars.__contains__, text).lower()

Funciona con cualquiera stro unicodeargumentos.

>>> only_lower_ascii_alpha("Hello there 123456!")
'hellothere'
>>> only_lower_ascii_alpha(u"435 café")
u'caf'
Respondida el 22/10/2011 a las 07:15
fuente por usuario tzot

votos
0

Personalmente usaría una expresión regular y luego convertiría la cadena final en minúscula. No tengo idea de cómo escribirlo en Python, pero la idea básica es:

  1. Elimina los caracteres en la cadena que no coinciden con expresiones regulares insensibles a mayúsculas y minúsculas " \w"
  2. Convertir cadena a minúscula

o viceversa.

Respondida el 12/03/2009 a las 03:44
fuente por usuario Dan Roberts


Aquí podría ser tu PUBLICIDAD