Jak wyodrębnić liczby z łańcucha w Pythonie?

432

Wyodrębniłbym wszystkie liczby zawarte w ciągu. Który lepiej nadaje się do celu, wyrażeń regularnych lub isdigit()metody?

Przykład:

line = "hello 12 hi 89"

Wynik:

[12, 89]
pablouche
źródło

Odpowiedzi:

485

Jeśli chcesz wyodrębnić tylko dodatnie liczby całkowite, spróbuj wykonać następujące czynności:

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

Argumentowałbym, że jest to lepsze niż przykład wyrażenia regularnego z trzech powodów. Po pierwsze, nie potrzebujesz innego modułu; po drugie, jest bardziej czytelny, ponieważ nie trzeba analizować mini-języka regex; i po trzecie, jest szybszy (a więc prawdopodobnie bardziej pythoniczny):

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

To nie rozpoznaje liczb zmiennoprzecinkowych, ujemnych liczb całkowitych lub liczb całkowitych w formacie szesnastkowym. Jeśli nie możesz zaakceptować tych ograniczeń, poniższa odpowiedź Slim rozwiąże problem.

fmark
źródło
5
to się nie powiedzie w przypadku „h3110 23 kot 444,4 królik 11-2 pies”
sharafjaffri
8
Wykorzystuje się przypadek normatywny re. Jest to ogólne i potężne narzędzie (dzięki czemu uczysz się czegoś bardzo przydatnego). Szybkość jest w pewnym stopniu nieistotna podczas analizowania dziennika (w końcu nie jest to intensywny solver numeryczny), remoduł znajduje się w standardowej bibliotece Pythona i ładowanie go nie boli.
Ioannis Filippidis
19
Miałem łańcuchy, mumblejumble45mumblejumblew których wiedziałem, że jest tylko jedna liczba. Rozwiązanie jest po prostu int(filter(str.isdigit, your_string)).
Jonas Lindeløv,
1
Drobny komentarz: definiujesz zmienną, strktóra następnie przesłania strobiekt i metodę w podstawowym pythonie. To nie jest dobra praktyka, ponieważ może być potrzebna później w skrypcie.
Jonas Lindeløv,
11
int(filter(...))podniesie TypeError: int() argument must be a string...dla Python 3.5, więc możesz użyć zaktualizowanej wersji: int(''.join(filter(str.isdigit, your_string)))do wyodrębnienia wszystkich cyfr do jednej liczby całkowitej.
Mark Mishyn
449

Użyłbym wyrażenia regularnego:

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

To również pasuje do 42 z bla42bla. Jeśli chcesz tylko liczby rozdzielane granicami słów (spacja, kropka, przecinek), możesz użyć \ b:

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

Aby skończyć z listą liczb zamiast listy ciągów:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
Vincent Savard
źródło
9
... a następnie zmapuj intgo i gotowe. +1, szczególnie w drugiej części. Sugerowałbym jednak nieprzetworzone ciągi ( r'\b\d+\b' == '\\b\\d+\\b').
5
Można go umieścić na liście z generatorem, na przykład:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt
7
@GreenMatt: technicznie jest to zrozumienie listy (nie generator), ale zgodziłbym się, że rozumienia / generatory są bardziej pytoniczne niż map.
Seth Johnson
1
@Seth Johnson: Ups! Masz rację, wpisałem błędny stan umysłu. :-( Dzięki za korektę!
GreenMatt,
2
Mam jednak problem. Co jeśli chcę wyodrębnić liczby zmiennoprzecinkowe również takie jak 1.45 w „hello1.45 hi”. Da mi 1 i 45 jako dwie różne liczby
ab123
89

Jest to więcej niż trochę za późno, ale możesz rozszerzyć wyrażenie regularne, aby uwzględnić również zapis naukowy.

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)

Daje wszystko dobrze!

Dodatkowo możesz spojrzeć na regex wbudowanego kleju AWS

aidan.plenert.macdonald
źródło
1
Ponieważ jest to jedyna odpowiedź, która każdemu się podoba, oto jak to zrobić z notacją naukową „[- +]? \ D + [\.]? \ D * [Ee]? \ D *”. Lub jakieś odmiany. Baw się dobrze!
aidan.plenert.macdonald
Znajdź, że występuje problem z najprostszym przypadkiem, np. Nie s = "4"zwraca żadnych wyników. Czy można ponownie edytować, aby się tym zająć?
batFINGER 10.10.16
1
fajnie, ale nie obsługuje przecinków (np.
74.600
Bardziej szczegółowa grupa to [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?grupa ta ma dać kilka fałszywych alarmów (tzn +zostaje schwytany przez siebie czasami), ale jest w stanie obsłużyć więcej form, takich jak .001, plus nie łączyć numery automatycznie (jak w s=2+1)
DavisDude
24
Ach tak, oczywiste [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- tak głupie z mojej strony ... jak mogłem o tym nie myśleć?
Przemek D
70

Zakładam, że chcesz liczb zmiennoprzecinkowych nie tylko liczb całkowitych, więc zrobiłbym coś takiego:

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

Pamiętaj, że niektóre inne opublikowane tutaj rozwiązania nie działają z liczbami ujemnymi:

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

>>> '-3'.isdigit()
False
jmnas
źródło
Znajduje dodatnie i ujemne liczby zmiennoprzecinkowe i liczby całkowite. Aby uzyskać tylko dodatnie i ujemne liczby całkowite, zmień floatna int.
Hugo,
3
Dla liczb ujemnych:re.findall("[-\d]+", "1 -2")
ytpillai
Czy robi to jakąkolwiek różnicę, jeśli piszemy continuezamiast passw pętli?
D. Jones
Łapie to więcej niż tylko dodatnie liczby całkowite, ale użycie split () spowoduje pominięcie liczb, które mają symbole waluty poprzedzające pierwszą cyfrę bez spacji, co jest powszechne w dokumentach finansowych
Marc Maxmeister
Nie działa dla liczb zmiennoprzecinkowych, które nie mają spacji z innymi znakami, na przykład: „4.5k rzeczy” zadziała, „4.5k rzeczy” nie zadziała.
Jay D.
64

Jeśli wiesz, że będzie to tylko jedna liczba w ciągu, tzn. „Witaj 12 cześć”, możesz spróbować przefiltrować.

Na przykład:

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

Ale bądź ostrożny !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
dfostyczny
źródło
12
W Pythonie 3.6.3 dostałem TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- naprawienie go za pomocąint("".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
źródło
3
Witamy w SO i dziękuję za opublikowanie odpowiedzi. Zawsze dobrą praktyką jest dodawanie dodatkowych komentarzy do odpowiedzi i wyjaśnienie, dlaczego to rozwiązuje problem, a nie tylko publikowanie fragmentu kodu.
sebs
nie działało w moim przypadku. niewiele różni się od powyższej odpowiedzi
oldboy
ValueError: nie można przekonwertować ciągu na zmiennoprzecinkowe: „e” i w niektórych przypadkach nie działa :(
Vilq
15

Szukałem rozwiązania do usuwania masek ciągów, szczególnie z brazylijskich numerów telefonów, ten post nie odpowiedział, ale zainspirował mnie. To jest moje rozwiązanie:

>>> phone_number = '+55(11)8715-9877'
>>> ''.join([n for n in phone_number if n.isdigit()])
'551187159877'
Sidon
źródło
12

Używanie Regex poniżej jest sposobem

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)

z 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
źródło
Powinieneś przynajmniej skompilować wyrażenie regularne, jeśli nie używaszfindall()
information_interchange
2
repl_str = re.compile('\d+.?\d*') powinno być: repl_str = re.compile('\d+\.?\d*') Dla odtwarzalnego przykładu z użyciem 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)

Cześć ,

możesz przeszukiwać wszystkie liczby całkowite w ciągu poprzez cyfrę, używając wyrażenia findall.

W drugim kroku utwórz listę res2 i dodaj do niej cyfry znalezione w ciągu

mam nadzieję że to pomoże

Pozdrawiam, Diwakar Sharma

Diwakar SHARMA
źródło
Podana odpowiedź została oznaczona do oceny jako post niskiej jakości. Oto kilka wskazówek, jak napisać dobrą odpowiedź? . Ta podana odpowiedź może być poprawna, ale może przydać się wyjaśnienie. Odpowiedzi tylko w kodzie nie są uważane za „dobre”. Z przeglądu .
Trenton McKinney
proste i działające rozwiązanie, docenione
moyo
7

Ta odpowiedź zawiera także przypadek, gdy liczba jest zmiennoprzecinkowa w ciągu

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
źródło
5

Dziwi mnie, że nikt jeszcze nie wspomniał o użyciu itertools.groupbyjako alternatywy do osiągnięcia tego celu.

Możesz użyć itertools.groupby()wraz z str.isdigit(), aby wyodrębnić liczby z łańcucha jako:

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]

Trzymana wartość lbędzie wynosić:

[12, 89]

PS: To jest wyłącznie w celu ilustracji, aby pokazać, że jako alternatywa moglibyśmy również groupbyto osiągnąć. Ale to nie jest zalecane rozwiązanie. Jeśli chcesz to osiągnąć, powinieneś użyć zaakceptowanej odpowiedzi fmark, opartej na używaniu rozumienia listy str.isdigitjako filtra.

Moinuddin Quadri
źródło
4

Właśnie dodam tę odpowiedź, ponieważ nikt nie dodał jej za pomocą obsługi wyjątków i ponieważ działa to również dla liczb zmiennoprzecinkowych

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

Wynik :

[1234.0, 56.78]
Raghav
źródło
3

Aby złapać różne wzorce, pomocne jest zapytanie o różne wzorce.

Skonfiguruj wszystkie wzorce, które wychwytują różne wzorce liczbowe:

(wyszukuje przecinki) 12,300 lub 12,300,00

„[\ d] + [., \ d] +”

(wyszukuje liczby zmiennoprzecinkowe) 0.123 lub .123

„[\ d] * [.] [\ d] +”

(znajduje liczby całkowite) 123

„[\ d] +”

Połącz z potokiem (|) w jeden wzór z wieloma lub warunkami warunkowymi .

(Uwaga: Najpierw umieść złożone wzory, w przeciwnym razie proste wzory zwrócą fragmenty złożonego połowu zamiast złożonego zwrotu zwracającego pełny połów).

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

Poniżej potwierdzimy obecność wzoru re.search(), a następnie zwrócimy iterowalną listę połowów. Na koniec wydrukujemy każdy haczyk za pomocą notacji nawiasowej, aby podselektywnie zwrócić wartość obiektu dopasowania z obiektu dopasowania.

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

Zwroty:

33
42
32
30
444.4
12,001
James Andrew Bush
źródło
2

Ponieważ żaden z nich nie dotyczył prawdziwych liczb finansowych w dokumentach Excel i Word, które musiałem znaleźć, oto moja odmiana. Obsługuje liczby całkowite, liczby zmiennoprzecinkowe, liczby ujemne, liczby walut (ponieważ nie odpowiada przy podziale) i ma opcję upuszczenia części dziesiętnej i po prostu zwraca liczby całkowite lub zwraca wszystko.

Obsługuje także system liczb Indian Laks, w którym przecinki pojawiają się nieregularnie, nie co 3 cyfry.

Nie obsługuje notacji naukowej ani liczb ujemnych umieszczonych w nawiasach w budżetach - będzie wyglądać na dodatnie.

Nie wyodrębnia również dat. Istnieją lepsze sposoby wyszukiwania dat w ciągach.

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
źródło
1

@jmnas, podobała mi się twoja odpowiedź, ale nie znalazłem pływaków. Pracuję nad skryptem do analizy kodu przechodzącego do frezarki CNC i potrzebowałem znaleźć zarówno X, jak i Y wymiary, które mogą być liczbami całkowitymi lub zmiennoprzecinkowymi, więc dostosowałem twój kod do następującego. Znajduje int, float z dodatnimi i ujemnymi vals. Nadal nie znajduje wartości w formacie szesnastkowym, ale możesz dodać „x” i „A” do „F” do num_charkrotki i myślę, że sparowałoby to rzeczy takie jak „0x23AC”.

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
źródło
0

Najlepsza opcja, którą znalazłem, jest poniżej. Wyodrębni liczbę i może wyeliminować dowolny typ znaku.

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
źródło
0

W przypadku numerów telefonów możesz po prostu wykluczyć wszystkie znaki inne niż cyfry z \ D w wyrażeniu regularnym:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Antonin GAVREL
źródło