Python'da bir dizeden sayıları nasıl ayıklayabilirim?


432

Bir dize içindeki tüm sayıları ayıklayacağım. Amaca, düzenli ifadelere veya isdigit()yönteme en uygun olan hangisidir?

Misal:

line = "hello 12 hi 89"

Sonuç:

[12, 89]

Yanıtlar:


485

Yalnızca pozitif tamsayıları ayıklamak istiyorsanız, aşağıdakileri deneyin:

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

Bunun üç nedenden dolayı normal ifade örneğinden daha iyi olduğunu iddia ediyorum. İlk olarak, başka bir modüle ihtiyacınız yoktur; ikincisi, daha okunaklıdır çünkü regex mini dilini ayrıştırmanıza gerek yoktur; ve üçüncüsü, daha hızlıdır (ve muhtemelen daha fazla pitoniktir):

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

Bu, kayan sayıları, negatif tam sayıları veya onaltılık biçimde tam sayıları tanımaz. Bu sınırlamaları kabul edemezseniz, slim'un aşağıdaki cevabı işe yarayacaktır.


5
Bu "h3110 23 kedi 444.4 tavşan 11-2 köpek" gibi durum için başarısız olacaktır
sharafjaffri

8
Normatif durum kullanıyor re. Genel ve güçlü bir araçtır (bu yüzden çok faydalı bir şey öğrenirsiniz). Günlük ayrıştırma işleminde hız biraz önemsizdir (sonuçta bazı yoğun sayısal çözücü değildir), remodül standart Python kitaplığındadır ve yüklemek zarar vermez.
Ioannis Filippidis

19
İçinde mumblejumble45mumblejumblesadece bir sayı olduğunu bildiğim dizeler vardı . Çözüm basittir int(filter(str.isdigit, your_string)).
Jonas Lindeløv

1
Küçük bir yorum: temel python'da nesneyi ve yöntemi strgeçersiz kılan değişkeni tanımlarsınız str. Bu daha iyi bir uygulama değildir çünkü senaryoda daha sonra ihtiyacınız olabilir.
Jonas Lindeløv

11
int(filter(...))TypeError: int() argument must be a string...Python 3.5 için yükselecektir , böylece güncellenmiş sürümü kullanabilirsiniz: int(''.join(filter(str.isdigit, your_string)))tüm basamakları tek bir tamsayıya çıkarmak için.
Mark Mishyn

449

Bir normal ifade kullanırdım:

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

Bu aynı zamanda şuradan 42 eşleşir bla42bla. Yalnızca kelime sınırlarıyla (boşluk, nokta, virgül) ayrılmış sayılar istiyorsanız \ b:

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

Dizeler listesi yerine bir sayı listesi bulmak için:

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

9
... ve sonra haritayı intçizdiğinizde işleminiz tamamlanmış demektir. Özellikle ikinci kısım için +1. Ham dizeleri ( r'\b\d+\b' == '\\b\\d+\\b') olsa öneririz .

5
Jeneratör içeren bir listeye konabilir, örneğin:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt

7
@GreenMatt: teknik olarak bir liste anlama (bir jeneratör değil), ama anlama / üreteçlerin daha Pythonic olduğu konusunda hemfikirim map.
Seth Johnson

1
@Seth Johnson: Hata! Haklısın, görünüşe göre buğulanmış bir ruh hali içinde yanlış yazdım. :-( Düzeltme için teşekkürler!
GreenMatt

2
Yine de bir sorunum var. "Merhaba1.45 merhaba" da 1.45 gibi kayan sayıları ayıklamak istersem ne olur? Bana iki farklı sayı olarak 1 ve 45 verecek
ab123

89

Bu biraz geç oldu, ancak bilimsel ifadeyi de hesaba katmak için normal ifade ifadesini genişletebilirsiniz.

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)

Her şeyi verir!

Ayrıca, AWS Glue yerleşik regex'e de bakabilirsiniz.


1
Herkesin sevdiği tek cevap budur, "[- +]? \ D + [\.]? \ D * [Ee]? \ D *" Bilimsel gösterim ile nasıl yapılacağı aşağıda açıklanmıştır. Veya bazı varyasyonlar. İyi eğlenceler!
aidan.plenert.macdonald

En basit durumla ilgili bir sorun olduğunu bulun, örneğin s = "4"eşleşme döndürmez. Bununla ilgilenmek için yeniden düzenlenebilir mi?
batFINGER

1
güzel ama virgüllerle uğraşmıyor (örneğin 74.600)
yekta

Daha ayrıntılı bir grup [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Bu grup bazı yanlış pozitifler verir (yani +bazen kendi başına yakalanır), ancak daha fazla formu işleyebilir, örneğin .001, sayıları otomatik olarak birleştirmez (olduğu gibi s=2+1)
DavisDude

24
Ah evet, bariz [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- çok aptalca ... bunu nasıl düşünemedim?
Przemek D

70

Ben sadece tamsayılar şamandıralar istediğiniz varsayıyorum bu yüzden böyle bir şey yapmak istiyorum:

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

Burada yayınlanan diğer çözümlerin bazılarının negatif sayılarla çalışmadığını unutmayın:

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

>>> '-3'.isdigit()
False

Bu pozitif ve negatif şamandıralar ve tamsayılar bulur. Sadece pozitif ve negatif tamsayılar, değişikliği için floatiçin int.
Hugo

3
Negatif sayılar için:re.findall("[-\d]+", "1 -2")
ytpillai

Döngü continueyerine yazarsak herhangi bir fark yaratır mı pass?
D. Jones

Bu, yalnızca pozitif tamsayılardan daha fazlasını yakalar, ancak split () kullanıldığında, ilk basamaktan önce para birimi simgeleri olmayan, finansal belgelerde yaygın olan
sayıları kaçıracaktır

Diğer karakterlerle boşluğu olmayan şamandıralar için çalışmaz, örneğin: '4.5 k şeyler' çalışacak, '4.5k şeyler' çalışmaz.
Jay

64

Dizede yalnızca bir sayı olacağını biliyorsanız, yani 'merhaba 12 merhaba', filtreyi deneyebilirsiniz.

Örneğin:

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

Ama dikkatli olun !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005

12
Python 3.6.3'te var TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- kullanarak sabitlemeint("".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]

3
SO'ya hoş geldiniz ve bir cevap gönderdiğiniz için teşekkürler. Yanıtınıza bazı kod açıklamaları eklemek yerine sorunun neden çözüldüğüne dair ek yorumlar eklemek her zaman iyi bir uygulamadır.
sebs

benim durumumda işe yaramadı. Yukarıdaki cevaptan çok farklı değil
oldboy

ValueError: string'i float'a dönüştüremedi: 'e' ve bazı durumlarda çalışmaz :(
Vilq

15

Özellikle Brezilya telefon numaralarından dizelerin maskelerini kaldırmak için bir çözüm arıyordum, bu yazı cevaplanmadı ama bana ilham verdi. Bu benim çözümüm:

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

12

Aşağıdaki Regex'i kullanmak

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)

findall ile 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']

Kullanmıyorsanız en azından normal ifadeyi derlemelisinizfindall()
information_interchange

2
repl_str = re.compile('\d+.?\d*') olması gereken: repl_str = re.compile('\d+\.?\d*') python3.7 re.search(re.compile(r'\d+.?\d*'), "42G").group() '42G' re.search(re.compile(r'\d+\.?\d*'), "42G").group() '42' kullanarak tekrarlanabilir bir örnek için
Alexis Lucattini

8
line2 = "hello 12 hi 89"
temp1 = re.findall(r'\d+', line2) # through regular expression
res2 = list(map(int, temp1))
print(res2)

Selam ,

findall ifadesini kullanarak dizedeki tüm tamsayıları basamaktan arayabilirsiniz.

İkinci adımda res2 listesi oluşturun ve dizede bulunan rakamları bu listeye ekleyin

Bu yardımcı olur umarım

Saygılarımızla, Diwakar Sharma


Verilen cevap, Düşük Kaliteli Yazı olarak incelenmek üzere işaretlendi. İşte için bazı kurallar vardır Ben iyi cevap yazmak nasıl? . Bu verilen cevap doğru olabilir, ancak bir açıklamadan yararlanabilir. Yalnızca kod yanıtları "iyi" yanıtlar olarak değerlendirilmez. Gönderen inceleme .
Trenton McKinney

basit ve çalışan bir çözüm, takdir
moyo

7

Bu yanıt ayrıca, sayı dizede kayar durumdayken de bulunur

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)

5

Bunu itertools.groupbybaşarmak için henüz kimsenin kullanımından bahsetmediğini görmek beni şaşırttı .

Dizeden sayıları aşağıdaki gibi ayıklamak için itertools.groupby()birlikte kullanabilirsiniz str.isdigit():

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]

Bekletme değeri l:

[12, 89]

Not: Bu sadece gösterim amacıyla, alternatif olarak bunu groupbybaşarmak için de kullanabileceğimizi göstermek içindir. Ancak bu önerilen bir çözüm değildir. Bunu başarmak istiyorsanız, liste kavrayışı filtre olarak kullanmaya dayalı olarak fmark'ın kabul edilen cevabını kullanmalısınız str.isdigit.


4

Ben sadece bu yanıtı ekliyorum çünkü kimse Exception işlemeyi kullanarak bir tane eklemedi ve bu aynı zamanda şamandıralar için de çalışıyor

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

Çıktı :

[1234.0, 56.78]

4

Farklı desenleri yakalamak için farklı desenlerle sorgulamak faydalıdır.

Farklı sayıdaki ilgi çekici kalıpları yakalayan tüm kalıpları ayarlayın:

(virgül bulur) 12,300 veya 12,300,00

[\ D] + [. \ D] + '

(şamandıraları bulur) 0.123 veya .123

[\ D] * [.] [\ D] + '

(tam sayıları bulur) 123

[\ D] + '

Boru (|) ile çoklu veya koşullu bir desene birleştirin .

(Not: Önce karmaşık kalıpları koyun, basit kalıplar tam yakalamayı döndüren karmaşık yakalama yerine karmaşık yakalamanın parçalarını döndürür).

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

Aşağıda, bir desenin bulunduğunu onaylayacağız re.search(), ardından tekrarlanabilir bir yakalama listesi döndüreceğiz. Son olarak, her bir yakalamayı, eşleme nesnesinden eşleme nesnesi dönüş değerini alt seçmek için köşeli ayraç gösterimini kullanarak yazdıracağız.

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

İadeler:

33
42
32
30
444.4
12,001

2

Bunların hiçbiri, bulmam gereken excel ve word belgelerindeki gerçek dünya finansal rakamlarıyla ilgilenmediğinden, işte benim varyasyonum. Ints, float, negatif sayılar, para birimi numaralarını (bölünmüş olarak yanıt vermediği için) işler ve ondalık kısmı bırakıp sadece iade veya her şeyi iade etme seçeneğine sahiptir.

Ayrıca virgüllerin düzensiz bir şekilde göründüğü Indian Laks sayı sistemini yönetir, her 3 rakamdan ayrı değildir.

Bütçe içindeki parantez içine konulan bilimsel gösterimi veya negatif sayıları ele almaz - olumlu görünecektir.

Ayrıca tarihleri ​​ayıklamaz. Dizelerde tarih bulmanın daha iyi yolları vardır.

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

1

@jmnas, cevabınızı beğendim, ama yüzer bulmadı. Ben bir CNC değirmen gidiyor kod ayrıştırmak için bir komut dosyası üzerinde çalışıyorum ve tamsayı veya kayan olabilir hem X ve Y boyutları bulmak için gerekli, bu yüzden aşağıdaki kod uyarlanmış. Bu pozitif ve negatif değerlerle int, float bulur. Hala onaltılık biçimlendirilmiş değerler bulamıyor ancak "x" ve "A" ile "F" ile num_chartuple ekleyebilirsiniz ve ben '0x23AC' gibi şeyleri ayrıştıracağını düşünüyorum.

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)

0

Bulduğum en iyi seçenek aşağıda. Bir sayı çıkarır ve herhangi bir karakter türünü ortadan kaldırabilir.

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)    

0

Telefon numaraları için, regex'te \ D olan rakam olmayan tüm karakterleri hariç tutabilirsiniz:

import re

phone_number = '(619) 459-3635'
phone_number = re.sub(r"\D", "", phone_number)
print(phone_number)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.