Wie extrahiere ich Zahlen aus einer Zeichenfolge in Python?

432

Ich würde alle in einer Zeichenfolge enthaltenen Zahlen extrahieren. Welches ist für den Zweck, reguläre Ausdrücke oder die isdigit()Methode besser geeignet ?

Beispiel:

line = "hello 12 hi 89"

Ergebnis:

[12, 89]
Pablouche
quelle

Antworten:

485

Wenn Sie nur positive Ganzzahlen extrahieren möchten, versuchen Sie Folgendes:

>>> str = "h3110 23 cat 444.4 rabbit 11 2 dog"
>>> [int(s) for s in str.split() if s.isdigit()]
[23, 11, 2]

Ich würde argumentieren, dass dies aus drei Gründen besser ist als das Regex-Beispiel. Erstens benötigen Sie kein weiteres Modul. Zweitens ist es besser lesbar, da Sie die Regex-Minisprache nicht analysieren müssen. und drittens ist es schneller (und damit wahrscheinlich pythonischer):

python -m timeit -s "str = 'h3110 23 cat 444.4 rabbit 11 2 dog' * 1000" "[s for s in str.split() if s.isdigit()]"
100 loops, best of 3: 2.84 msec per loop

python -m timeit -s "import re" "str = 'h3110 23 cat 444.4 rabbit 11 2 dog' * 1000" "re.findall('\\b\\d+\\b', str)"
100 loops, best of 3: 5.66 msec per loop

Dies erkennt keine Gleitkommazahlen, negativen Ganzzahlen oder Ganzzahlen im Hexadezimalformat. Wenn Sie diese Einschränkungen nicht akzeptieren können, reicht die unten stehende Antwort von slim aus .

fmark
quelle
5
Dies wird für Fälle wie "h3110 23 Katze 444.4 Kaninchen 11-2 Hund"
sharafjaffri
8
Der normative Fall verwendet re. Es ist ein allgemeines und leistungsfähiges Werkzeug (damit Sie etwas sehr Nützliches lernen). Die Geschwindigkeit spielt beim Parsen von Protokollen keine Rolle (es ist schließlich kein intensiver numerischer Löser). Das reModul befindet sich in der Standard-Python-Bibliothek und es tut nicht weh, es zu laden.
Ioannis Filippidis
19
Ich hatte solche Saiten, mumblejumble45mumblejumblein denen ich wusste, dass es nur eine Nummer gab. Die Lösung ist einfach int(filter(str.isdigit, your_string)).
Jonas Lindeløv
1
Ein kleiner Kommentar: Sie definieren die Variable, strdie dann das strObjekt und die Methode in Base Python überschreibt . Dies ist keine gute Vorgehensweise, da Sie sie möglicherweise später im Skript benötigen.
Jonas Lindeløv
11
int(filter(...))wird TypeError: int() argument must be a string...für Python 3.5 ausgelöst, sodass Sie die aktualisierte Version verwenden können: int(''.join(filter(str.isdigit, your_string)))zum Extrahieren aller Ziffern in eine Ganzzahl.
Mark Mishyn
448

Ich würde einen regulären Ausdruck verwenden:

>>> import re
>>> re.findall(r'\d+', 'hello 42 I\'m a 32 string 30')
['42', '32', '30']

Dies würde auch 42 von entsprechen bla42bla. Wenn Sie nur Zahlen möchten, die durch Wortgrenzen (Leerzeichen, Punkt, Komma) begrenzt sind, können Sie \ b verwenden:

>>> re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')
['42', '32', '30']

So erhalten Sie eine Liste mit Zahlen anstelle einer Liste mit Zeichenfolgen:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
Vincent Savard
quelle
9
... und dann abbilden intund fertig. +1 speziell für den letzten Teil. Ich würde allerdings rohe Strings ( r'\b\d+\b' == '\\b\\d+\\b') vorschlagen .
5
Es könnte in eine Liste mit einem Generator aufgenommen werden, wie zum Beispiel:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt
7
@ GreenMatt: Das ist technisch gesehen ein Listenverständnis (kein Generator), aber ich würde zustimmen, dass Verständnis / Generatoren mehr pythonisch sind als map.
Seth Johnson
1
@ Seth Johnson: Ups! Du hast recht, ich habe mich in einem scheinbar beschlagenen Geisteszustand getippt. :-( Danke für die Korrektur!
GreenMatt
2
Ich habe allerdings ein Problem. Was ist, wenn ich Float-Zahlen wie 1.45 in "hello1.45 hi" extrahieren möchte? Es wird mir 1 und 45 als zwei verschiedene Zahlen geben
ab123
89

Dies ist mehr als ein bisschen spät, aber Sie können den Regex-Ausdruck erweitern, um auch die wissenschaftliche Notation zu berücksichtigen.

import re

# Format is [(<string>, <expected output>), ...]
ss = [("apple-12.34 ba33na fanc-14.23e-2yapple+45e5+67.56E+3",
       ['-12.34', '33', '-14.23e-2', '+45e5', '+67.56E+3']),
      ('hello X42 I\'m a Y-32.35 string Z30',
       ['42', '-32.35', '30']),
      ('he33llo 42 I\'m a 32 string -30', 
       ['33', '42', '32', '-30']),
      ('h3110 23 cat 444.4 rabbit 11 2 dog', 
       ['3110', '23', '444.4', '11', '2']),
      ('hello 12 hi 89', 
       ['12', '89']),
      ('4', 
       ['4']),
      ('I like 74,600 commas not,500', 
       ['74,600', '500']),
      ('I like bad math 1+2=.001', 
       ['1', '+2', '.001'])]

for s, r in ss:
    rr = re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", s)
    if rr == r:
        print('GOOD')
    else:
        print('WRONG', rr, 'should be', r)

Gibt alles gut!

Darüber hinaus können Sie sich den in AWS Glue integrierten regulären Ausdruck ansehen

aidan.plenert.macdonald
quelle
1
Da dies die einzige Antwort ist, die jemand mag, erfahren Sie hier, wie Sie dies mit der wissenschaftlichen Notation "[- +]? \ D + [\.]? \ D * [Ee]? \ D *" tun. Oder eine Variation. Habe Spaß!
aidan.plenert.macdonald
Finden Sie heraus, dass es ein Problem mit dem einfachsten Fall gibt, z s = "4". B. gibt es keine Übereinstimmungen zurück. Kann erneut bearbeitet werden, um dies auch zu erledigen?
BatFINGER
1
schön, aber es geht nicht um Kommas (zB 74.600)
yekta
Eine ausführlichere Gruppe ist [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Diese Gruppe gibt einige Fehlalarme aus (dh +wird manchmal von selbst erfasst), kann jedoch mehr Formulare verarbeiten, z. B. .001und kombiniert Zahlen nicht automatisch (wie in s=2+1)
DavisDude
23
Ah ja, das Offensichtliche [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- so dumm von mir ... wie konnte ich mir das nicht vorstellen?
Przemek D
70

Ich gehe davon aus, dass Sie Floats nicht nur ganze Zahlen wollen, also würde ich so etwas tun:

l = []
for t in s.split():
    try:
        l.append(float(t))
    except ValueError:
        pass

Beachten Sie, dass einige der anderen hier veröffentlichten Lösungen nicht mit negativen Zahlen funktionieren:

>>> re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string -30')
['42', '32', '30']

>>> '-3'.isdigit()
False
jmnas
quelle
Dies findet positive und negative Gleitkommazahlen und ganze Zahlen. Ändern Sie für nur positive und negative Ganzzahlen floatzu int.
Hugo
3
Für negative Zahlen:re.findall("[-\d]+", "1 -2")
Ytpillai
Macht es einen Unterschied, ob wir continuestatt passin der Schleife schreiben ?
D. Jones
Dies fängt mehr als nur positive ganze Zahlen ab, aber bei Verwendung von split () werden Zahlen übersehen, deren Währungssymbole vor der ersten Ziffer ohne Leerzeichen stehen, was in Finanzdokumenten üblich ist
Marc Maxmeister
Funktioniert nicht für Floats, die keinen Platz für andere Zeichen haben. Beispiel: '4.5 k Dinge' funktionieren, '4.5k Dinge' nicht.
Jay D.
64

Wenn Sie wissen, dass es nur eine Zahl in der Zeichenfolge ist, dh "Hallo 12 Hallo", können Sie versuchen, zu filtern.

Zum Beispiel:

In [1]: int(''.join(filter(str.isdigit, '200 grams')))
Out[1]: 200
In [2]: int(''.join(filter(str.isdigit, 'Counters: 55')))
Out[2]: 55
In [3]: int(''.join(filter(str.isdigit, 'more than 23 times')))
Out[3]: 23

Aber sei vorsichtig !!! ::

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
dfostic
quelle
12
In Python 3.6.3 habe ich TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- es mitint("".join(filter(str.isdigit, '200 grams')))
Kent Munthe Caspersen
16
# extract numbers from garbage string:
s = '12//n,_@#$%3.14kjlw0xdadfackvj1.6e-19&*ghn334'
newstr = ''.join((ch if ch in '0123456789.-e' else ' ') for ch in s)
listOfNumbers = [float(i) for i in newstr.split()]
print(listOfNumbers)
[12.0, 3.14, 0.0, 1.6e-19, 334.0]
AndreiS
quelle
3
Willkommen bei SO und vielen Dank für die Antwort. Es ist immer empfehlenswert, Ihrer Antwort einige zusätzliche Kommentare hinzuzufügen und zu erklären, warum das Problem dadurch gelöst wird, anstatt nur ein Code-Snippet zu veröffentlichen.
sebs
hat in meinem Fall nicht funktioniert. nicht viel anders als die Antwort oben
Oldboy
ValueError: String konnte nicht in float konvertiert werden: 'e' und es funktioniert in einigen Fällen nicht :(
Vilq
15

Ich suchte nach einer Lösung, um die Masken von Strings zu entfernen, insbesondere von brasilianischen Telefonnummern. Dieser Beitrag wurde nicht beantwortet, hat mich aber inspiriert. Das ist meine Lösung:

>>> phone_number = '+55(11)8715-9877'
>>> ''.join([n for n in phone_number if n.isdigit()])
'551187159877'
Sidon
quelle
12

Die Verwendung von Regex unten ist der Weg

lines = "hello 12 hi 89"
import re
output = []
#repl_str = re.compile('\d+.?\d*')
repl_str = re.compile('^\d+$')
#t = r'\d+.?\d*'
line = lines.split()
for word in line:
        match = re.search(repl_str, word)
        if match:
            output.append(float(match.group()))
print (output)

mit findall re.findall(r'\d+', "hello 12 hi 89")

['12', '89']

re.findall(r'\b\d+\b', "hello 12 hi 89 33F AC 777")

 ['12', '89', '777']
sim
quelle
Sie sollten mindestens den regulären Ausdruck kompilieren, wenn Sie nicht verwendenfindall()
information_interchange
2
repl_str = re.compile('\d+.?\d*') sollte sein: repl_str = re.compile('\d+\.?\d*') Für ein reproduzierbares Beispiel mit Python3.7 re.search(re.compile(r'\d+.?\d*'), "42G").group() '42G' re.search(re.compile(r'\d+\.?\d*'), "42G").group() '42'
Alexis Lucattini
8
line2 = "hello 12 hi 89"
temp1 = re.findall(r'\d+', line2) # through regular expression
res2 = list(map(int, temp1))
print(res2)

Hallo ,

Sie können alle Ganzzahlen in der Zeichenfolge durch die Ziffer durchsuchen, indem Sie den Ausdruck findall verwenden.

Erstellen Sie im zweiten Schritt eine Liste res2 und fügen Sie die in der Zeichenfolge enthaltenen Ziffern zu dieser Liste hinzu

hoffe das hilft

Grüße, Diwakar Sharma

Diwakar SHARMA
quelle
Die bereitgestellte Antwort wurde zur Überprüfung als Beitrag von geringer Qualität gekennzeichnet. Hier sind einige Richtlinien für Wie schreibe ich eine gute Antwort? . Diese Antwort mag richtig sein, könnte aber von einer Erklärung profitieren. Nur-Code-Antworten gelten nicht als "gute" Antworten. Aus der Überprüfung .
Trenton McKinney
einfache und funktionierende Lösung, geschätzt
moyo
7

Diese Antwort enthält auch den Fall, dass die Zahl in der Zeichenfolge schwebt

def get_first_nbr_from_str(input_str):
    '''
    :param input_str: strings that contains digit and words
    :return: the number extracted from the input_str
    demo:
    'ab324.23.123xyz': 324.23
    '.5abc44': 0.5
    '''
    if not input_str and not isinstance(input_str, str):
        return 0
    out_number = ''
    for ele in input_str:
        if (ele == '.' and '.' not in out_number) or ele.isdigit():
            out_number += ele
        elif out_number:
            break
    return float(out_number)
Menglong Li
quelle
5

Ich bin erstaunt zu sehen, dass noch niemand die Verwendung von erwähnt hat itertools.groupby als Alternative erwähnt hat, um dies zu erreichen.

Sie können itertools.groupby()zusammen mit str.isdigit()verwenden, um Zahlen aus der Zeichenfolge zu extrahieren:

from itertools import groupby
my_str = "hello 12 hi 89"

l = [int(''.join(i)) for is_digit, i in groupby(my_str, str.isdigit) if is_digit]

Der gehaltene Wert list:

[12, 89]

PS: Dies dient nur zur Veranschaulichung, um zu zeigen, dass wir als Alternative auch groupbydies erreichen können. Dies ist jedoch keine empfohlene Lösung. Wenn Sie dies erreichen möchten, sollten Sie eine akzeptierte Antwort von fmark verwenden, die auf der Verwendung des Listenverständnisses mit str.isdigitals Filter basiert .

Moinuddin Quadri
quelle
4

Ich füge diese Antwort nur hinzu, weil niemand eine mit Ausnahmebehandlung hinzugefügt hat und weil dies auch für Floats funktioniert

a = []
line = "abcd 1234 efgh 56.78 ij"
for word in line.split():
    try:
        a.append(float(word))
    except ValueError:
        pass
print(a)

Ausgabe :

[1234.0, 56.78]
Raghav
quelle
3

Um unterschiedliche Muster zu erfassen, ist es hilfreich, mit unterschiedlichen Mustern abzufragen.

Richten Sie alle Muster ein, die unterschiedliche interessierende Zahlenmuster erfassen:

(findet Kommas) 12.300 oder 12.300,00

'[\ d] + [., \ d] +'

(findet Schwimmer) 0.123 oder .123

'[\ d] * [.] [\ d] +'

(findet ganze Zahlen) 123

'[\ d] +'

Kombinieren Sie mit Rohr (|) zu einem Muster mit mehreren oder Bedingungen .

(Hinweis: Setzen Sie komplexe Muster an die erste Stelle. Andernfalls geben einfache Muster Teile des komplexen Fangs zurück, anstatt dass der komplexe Fang den vollen Fang zurückgibt.)

p = '[\d]+[.,\d]+|[\d]*[.][\d]+|[\d]+'

Im Folgenden bestätigen wir, dass ein Muster vorhanden ist re.search(), und geben dann eine iterierbare Liste der Fänge zurück. Schließlich drucken wir jeden Fang in Klammernotation, um den Rückgabewert des Übereinstimmungsobjekts aus dem Übereinstimmungsobjekt auszuwählen.

s = 'he33llo 42 I\'m a 32 string 30 444.4 12,001'

if re.search(p, s) is not None:
    for catch in re.finditer(p, s):
        print(catch[0]) # catch is a match object

Kehrt zurück:

33
42
32
30
444.4
12,001
James Andrew Bush
quelle
2

Da sich keines davon mit realen Finanzzahlen in Excel- und Word-Dokumenten befasste, die ich finden musste, ist hier meine Variation. Es verarbeitet Ints, Floats, negative Zahlen und Währungszahlen (da es beim Teilen nicht antwortet) und hat die Option, den Dezimalteil zu löschen und nur Ints zurückzugeben oder alles zurückzugeben.

Es behandelt auch das indische Laks-Zahlensystem, bei dem Kommas unregelmäßig erscheinen und nicht alle drei Zahlen voneinander entfernt sind.

Es behandelt keine wissenschaftliche Notation oder negative Zahlen in Klammern in Budgets - wird positiv erscheinen.

Es werden auch keine Daten extrahiert. Es gibt bessere Möglichkeiten, Datumsangaben in Zeichenfolgen zu finden.

import re
def find_numbers(string, ints=True):            
    numexp = re.compile(r'[-]?\d[\d,]*[\.]?[\d{2}]*') #optional - in front
    numbers = numexp.findall(string)    
    numbers = [x.replace(',','') for x in numbers]
    if ints is True:
        return [int(x.replace(',','').split('.')[0]) for x in numbers]            
    else:
        return numbers
Marc Maxmeister
quelle
1

@jmnas, ich mochte Ihre Antwort, aber es wurden keine Schwimmer gefunden. Ich arbeite an einem Skript zum Parsen von Code, der zu einer CNC-Fräse geht, und musste sowohl X- als auch Y-Dimensionen finden, die Ganzzahlen oder Gleitkommazahlen sein können. Deshalb habe ich Ihren Code an Folgendes angepasst. Dies findet int, float mit positiven und negativen Werten. Es werden immer noch keine hexadezimal formatierten Werte gefunden, aber Sie könnten dem num_charTupel "x" und "A" bis "F" hinzufügen, und ich denke, es würde Dinge wie '0x23AC' analysieren.

s = 'hello X42 I\'m a Y-32.35 string Z30'
xy = ("X", "Y")
num_char = (".", "+", "-")

l = []

tokens = s.split()
for token in tokens:

    if token.startswith(xy):
        num = ""
        for char in token:
            # print(char)
            if char.isdigit() or (char in num_char):
                num = num + char

        try:
            l.append(float(num))
        except ValueError:
            pass

print(l)
ZacSketches
quelle
0

Die beste Option, die ich gefunden habe, ist unten. Es extrahiert eine Zahl und kann jede Art von Zeichen entfernen.

def extract_nbr(input_str):
    if input_str is None or input_str == '':
        return 0

    out_number = ''
    for ele in input_str:
        if ele.isdigit():
            out_number += ele
    return float(out_number)    
Ajay Kumar
quelle
0

Bei Telefonnummern können Sie einfach alle nichtstelligen Zeichen mit \ D in Regex ausschließen:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Antonin GAVREL
quelle