Usuwanie wszystkiego oprócz znaków alfanumerycznych z ciągu znaków w Pythonie


337

Jaki jest najlepszy sposób na usunięcie wszystkich znaków alfanumerycznych z ciągu przy użyciu Pythona?

Rozwiązania przedstawione w wariancie PHP tego pytania prawdopodobnie będą działać z pewnymi drobnymi poprawkami, ale nie wydają mi się zbyt „pytoniczne”.

Dla przypomnienia, nie chcę tylko usuwać kropek i przecinków (i innych znaków interpunkcyjnych), ale także cytatów, nawiasów itp.


7
Czy zależy Ci na międzynarodowych znakach alfanumerycznych, takich jak „æøå”, „مرحبا”, „สวัสดี”, „こ ん に ち は”?
Pimin Konstantin Kefaloukos

4
@PiminKonstantinKefaloukos Tak. Dbam o znaki międzynarodowe, stąd mój komentarz do zaakceptowanej odpowiedzi na użycie re.UNICODE.
Mark van Lent

Odpowiedzi:


336

Właśnie przeliczyłem niektóre funkcje z ciekawości. W tych testach usuwam znaki niealfanumeryczne z łańcucha string.printable(część wbudowanego stringmodułu). Zastosowanie skompilowanego '[\W_]+'i pattern.sub('', str)okazało się najszybsze.

$ python -m timeit -s \
     "import string" \
     "''.join(ch for ch in string.printable if ch.isalnum())" 
10000 loops, best of 3: 57.6 usec per loop

$ python -m timeit -s \
    "import string" \
    "filter(str.isalnum, string.printable)"                 
10000 loops, best of 3: 37.9 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]', '', string.printable)"
10000 loops, best of 3: 27.5 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]+', '', string.printable)"                
100000 loops, best of 3: 15 usec per loop

$ python -m timeit -s \
    "import re, string; pattern = re.compile('[\W_]+')" \
    "pattern.sub('', string.printable)" 
100000 loops, best of 3: 11.2 usec per loop

2
Bardzo interesujące wyniki: oczekiwałbym, że wyrażenia regularne będą wolniejsze. Co ciekawe, wypróbowałem to z jedną inną opcją ( valid_characters = string.ascii_letters + string.digitsnastępnie join(ch for ch in string.printable if ch in valid_characters)było to o 6 mikrosekund szybsze niż isalnum()opcja. Wciąż jednak znacznie wolniejsze niż regexp.
DrAl

+1, pomiar czasu jest dobry! (ale w przedostatnim, pattern.sub('', string.printable)zamiast tego - głupio wywołać re.sub, gdy masz obiekt RE! -).
Alex Martelli,

46
Dla przypomnienia: użyj, re.compile('[\W_]+', re.UNICODE)aby uczynić go bezpiecznym dla Unicode.
Mark van Lent

3
jak to zrobić bez usuwania białej przestrzeni?
maudulus

6
zrób to bez usuwania białych znaków: re.sub ('[\ W _] +', '', zdanie, flags = re.UNICODE)
PALEN 26.04.17

268

Wyrażenia regularne na ratunek:

import re
re.sub(r'\W+', '', your_string)

Według definicji w języku Python '\W== [^a-zA-Z0-9_], która wyklucza wszystkie numbers, lettersoraz_


2
Co robi znak plus w wyrażeniu regularnym? (Wiem, co to znaczy, po prostu ciekawi mnie, dlaczego jest to potrzebne do re.)
Mark van Lent

7
@Mark: Wyobrażam sobie, że przyspieszyłoby to podstawienie, ponieważ zamiana pozbywa się wszystkich znaków niebędących wyrazami w bloku za jednym razem, zamiast usuwać je jeden po drugim.
DrAl

2
Tak, sprawdziłem to jakiś czas temu podczas dostrajania kodu o kluczowym znaczeniu dla wydajności. Jeśli istnieją znaczne rozpiętości postaci do zastąpienia, przyspieszenie jest ogromne.
Ants Aasma

20
W tym przypadku może nie być to istotne, ale \Wzachowa również podkreślenia.
Blixt,

12
Zgodnie z wskazówką @Blixt, jeśli chcesz tylko litery i cyfry, możesz zrobić re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Nigini

68

Użyj str.translate () .

Zakładając, że będziesz to robił często:

(1) Raz utwórz ciąg zawierający wszystkie znaki, które chcesz usunąć:

delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())

(2) Ilekroć chcesz skrobać ciąg:

scrunched = s.translate(None, delchars)

Koszt instalacji prawdopodobnie porównuje się korzystnie z re.compile; koszt krańcowy jest znacznie niższy:

C:\junk>\python26\python -mtimeit -s"import string;d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s=string.printable" "s.translate(None,d)"
100000 loops, best of 3: 2.04 usec per loop

C:\junk>\python26\python -mtimeit -s"import re,string;s=string.printable;r=re.compile(r'[\W_]+')" "r.sub('',s)"
100000 loops, best of 3: 7.34 usec per loop

Uwaga: użycie string.printable jako danych porównawczych daje wzorowi „[\ W _] +” nieuczciwą przewagę ; wszystkie znaki niealfanumeryczne znajdują się w jednej wiązce ... w typowych danych do wykonania byłoby więcej niż jedno podstawienie:

C:\junk>\python26\python -c "import string; s = string.printable; print len(s),repr(s)"
100 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Oto, co się stanie, jeśli dasz re.sub nieco więcej pracy:

C:\junk>\python26\python -mtimeit -s"d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s='foo-'*25" "s.translate(None,d)"
1000000 loops, best of 3: 1.97 usec per loop

C:\junk>\python26\python -mtimeit -s"import re;s='foo-'*25;r=re.compile(r'[\W_]+')" "r.sub('',s)"
10000 loops, best of 3: 26.4 usec per loop

1
Korzystanie z tłumaczenia jest rzeczywiście nieco szybsze. Nawet po dodaniu pętli for bezpośrednio przed wykonaniem zamiany / tłumaczenia (aby koszty konfiguracji ważyły ​​mniej) nadal sprawia, że ​​tłumaczenie jest około 17 razy szybsze niż wyrażenie regularne na moim komputerze. Dobrze wiedzieć.
Mark van Lent

3
To zdecydowanie najbardziej pytoniczne rozwiązanie.
codygman

1
To mnie prawie przekonuje, ale sugerowałbym użycie string.punctuationZamiast''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols

1
Zauważ, że działa to dla strobiektów, ale nie dla unicodeobiektów.
Yavar

@John Machin Czy to w zasadzie rozumienie listy, które jest przekazywane jako argument .join()?
AdjunctProfessorFalcon

41

Możesz spróbować:

print ''.join(ch for ch in some_string if ch.isalnum())

15
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13

Podobało mi się twoja odpowiedź, ale usuwa też znaki arabskie. Czy możesz mi powiedzieć, jak je zachować
Charif DZ

13

Co powiesz na:

def ExtractAlphanumeric(InputString):
    from string import ascii_letters, digits
    return "".join([ch for ch in InputString if ch in (ascii_letters + digits)])

Działa to poprzez użycie rozumienia listy w celu utworzenia listy znaków, InputStringjeśli są one obecne w połączonych ascii_lettersi digitsciągach. Następnie łączy listę razem w ciąg.


Wygląda na to, że string.ascii_letters zawiera tylko litery (duh), a nie cyfry. Potrzebuję też liczb ...
Mark van Lent

Dodanie string.digits rzeczywiście rozwiązałoby problem, o którym właśnie wspomniałem. :)
Mark van Lent

Tak, zdałem sobie sprawę, że kiedy wróciłem, aby przeczytać twoje pytanie. Uwaga do siebie: naucz się czytać!
DrAl

4

Jako część innych odpowiedzi tutaj, oferuję naprawdę prosty i elastyczny sposób na zdefiniowanie zestawu znaków, do których chcesz ograniczyć zawartość łańcucha. W tym przypadku zezwalam na myślnik alfanumeryczny PLUS i podkreślenie. Po prostu dodaj lub usuń znaki z mojego, PERMITTED_CHARSjak pasuje do twojego przypadku użycia.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)

2
Zamiast kodować dozwolone znaki, które są podatne na subtelne błędy, użyj string.digits + string.ascii_letters + '_-'.
Reti43,

Twoja sugestia nie jest zła, ale nie oszczędza też wielu znaków „pisania”, jeśli to jest twój cel. Jeśli skopiujesz mój post, nie będziesz miał literówki! Prawdziwym punktem mojej odpowiedzi jest jednak umożliwienie jawnego, otwartego i prostego sposobu na dokładne określenie, które znaki chcesz dopuścić.
BuvinJ

Jako środek, możesz połączyć te sugestie, SPECIAL_CHARS = '_-'a następnie użyćstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ,

To była sugestia pod względem tego, co jest rozsądne, chyba że uprawiamy golfa kodowego. „Spacerowanie” po klawiaturze w celu wpisania 52 liter alfabetu w kolejności zajmuje znacznie więcej czasu niż zaimportowanie pakietu w celu użycia jednego lub dwóch obiektów. I to nie obejmuje czasu na sprawdzenie, czy wszystko wpisałeś poprawnie. Chodzi o dobre praktyki, to wszystko.
Reti43,

Słyszę cię! Moim prawdziwym punktem jest tutaj wyjątkowa elastyczność, na wypadek, gdybyś chciał bardziej szczegółowo określić swój zestaw postaci.
BuvinJ

4
sent = "".join(e for e in sent if e.isalpha())

Spróbuję wyjaśnić: przechodzi przez wszystkie znaki ciągu e for e in senti sprawdza poprzez if e.isalpha()instrukcję, czy bieżący znak jest symbolem alfabetycznym, jeśli tak - łączy go ze sentzmienną przez sent = "".join()i wszystkie symbole niealfabetyczne zostaną zastąpione ""(pusty ciąg), ponieważ z joinfunkcji.
Sysanin

ponieważ robi to pętlę na postać, zamiast polegać na wyrażeniu regularnym C, czy nie jest to wyjątkowo wolne?
dcsan

3
for char in my_string:
    if not char.isalnum():
        my_string = my_string.replace(char,"")

2

Czasy losowych ciągów drukowalnych ASCII:

from inspect import getsource
from random import sample
import re
from string import printable
from timeit import timeit

pattern_single = re.compile(r'[\W]')
pattern_repeat = re.compile(r'[\W]+')
translation_tb = str.maketrans('', '', ''.join(c for c in map(chr, range(256)) if not c.isalnum()))


def generate_test_string(length):
    return ''.join(sample(printable, length))


def main():
    for i in range(0, 60, 10):
        for test in [
            lambda: ''.join(c for c in generate_test_string(i) if c.isalnum()),
            lambda: ''.join(filter(str.isalnum, generate_test_string(i))),
            lambda: re.sub(r'[\W]', '', generate_test_string(i)),
            lambda: re.sub(r'[\W]+', '', generate_test_string(i)),
            lambda: pattern_single.sub('', generate_test_string(i)),
            lambda: pattern_repeat.sub('', generate_test_string(i)),
            lambda: generate_test_string(i).translate(translation_tb),

        ]:
            print(timeit(test), i, getsource(test).lstrip('            lambda: ').rstrip(',\n'), sep='\t')


if __name__ == '__main__':
    main()

Wynik (Python 3.7):

       Time       Length                           Code                           
6.3716264850008880  00  ''.join(c for c in generate_test_string(i) if c.isalnum())
5.7285426190064750  00  ''.join(filter(str.isalnum, generate_test_string(i)))
8.1875841680011940  00  re.sub(r'[\W]', '', generate_test_string(i))
8.0002205439959650  00  re.sub(r'[\W]+', '', generate_test_string(i))
5.5290945199958510  00  pattern_single.sub('', generate_test_string(i))
5.4417179649972240  00  pattern_repeat.sub('', generate_test_string(i))
4.6772285089973590  00  generate_test_string(i).translate(translation_tb)
23.574712151996210  10  ''.join(c for c in generate_test_string(i) if c.isalnum())
22.829975890002970  10  ''.join(filter(str.isalnum, generate_test_string(i)))
27.210196289997840  10  re.sub(r'[\W]', '', generate_test_string(i))
27.203713296003116  10  re.sub(r'[\W]+', '', generate_test_string(i))
24.008979928999906  10  pattern_single.sub('', generate_test_string(i))
23.945240008994006  10  pattern_repeat.sub('', generate_test_string(i))
21.830899796994345  10  generate_test_string(i).translate(translation_tb)
38.731336012999236  20  ''.join(c for c in generate_test_string(i) if c.isalnum())
37.942474347000825  20  ''.join(filter(str.isalnum, generate_test_string(i)))
42.169366310001350  20  re.sub(r'[\W]', '', generate_test_string(i))
41.933375883003464  20  re.sub(r'[\W]+', '', generate_test_string(i))
38.899814646996674  20  pattern_single.sub('', generate_test_string(i))
38.636144253003295  20  pattern_repeat.sub('', generate_test_string(i))
36.201238164998360  20  generate_test_string(i).translate(translation_tb)
49.377356811004574  30  ''.join(c for c in generate_test_string(i) if c.isalnum())
48.408927293996385  30  ''.join(filter(str.isalnum, generate_test_string(i)))
53.901889764994850  30  re.sub(r'[\W]', '', generate_test_string(i))
52.130339455994545  30  re.sub(r'[\W]+', '', generate_test_string(i))
50.061149017004940  30  pattern_single.sub('', generate_test_string(i))
49.366573111998150  30  pattern_repeat.sub('', generate_test_string(i))
46.649754120997386  30  generate_test_string(i).translate(translation_tb)
63.107938601999194  40  ''.join(c for c in generate_test_string(i) if c.isalnum())
65.116287978999030  40  ''.join(filter(str.isalnum, generate_test_string(i)))
71.477421126997800  40  re.sub(r'[\W]', '', generate_test_string(i))
66.027950693998720  40  re.sub(r'[\W]+', '', generate_test_string(i))
63.315361931003280  40  pattern_single.sub('', generate_test_string(i))
62.342320287003530  40  pattern_repeat.sub('', generate_test_string(i))
58.249303059004890  40  generate_test_string(i).translate(translation_tb)
73.810345625002810  50  ''.join(c for c in generate_test_string(i) if c.isalnum())
72.593953348005020  50  ''.join(filter(str.isalnum, generate_test_string(i)))
76.048324580995540  50  re.sub(r'[\W]', '', generate_test_string(i))
75.106637657001560  50  re.sub(r'[\W]+', '', generate_test_string(i))
74.681338128997600  50  pattern_single.sub('', generate_test_string(i))
72.430461594005460  50  pattern_repeat.sub('', generate_test_string(i))
69.394243567003290  50  generate_test_string(i).translate(translation_tb)

str.maketransi str.translatejest najszybszy, ale obejmuje wszystkie znaki spoza ASCII. re.compilei pattern.subjest wolniejszy, ale jest jakoś szybszy niż ''.join& filter.


-1

Jeśli dobrze zrozumiałem, najłatwiejszym sposobem jest użycie wyrażenia regularnego, ponieważ zapewnia ono dużą elastyczność, ale inną prostą metodą jest użycie pętli za kodem z przykładem. Policzyłem również występowanie słowa i zachowałem je w słowniku.

s = """An... essay is, generally, a piece of writing that gives the author's own 
argument — but the definition is vague, 
overlapping with those of a paper, an article, a pamphlet, and a short story. Essays 
have traditionally been 
sub-classified as formal and informal. Formal essays are characterized by "serious 
purpose, dignity, logical 
organization, length," whereas the informal essay is characterized by "the personal 
element (self-revelation, 
individual tastes and experiences, confidential manner), humor, graceful style, 
rambling structure, unconventionality 
or novelty of theme," etc.[1]"""

d = {}      # creating empty dic      
words = s.split() # spliting string and stroing in list
for word in words:
    new_word = ''
    for c in word:
        if c.isalnum(): # checking if indiviual chr is alphanumeric or not
            new_word = new_word + c
    print(new_word, end=' ')
    # if new_word not in d:
    #     d[new_word] = 1
    # else:
    #     d[new_word] = d[new_word] +1
print(d)

oceń to, jeśli ta odpowiedź jest przydatna!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.