Comment extraire des nombres d'une chaîne en Python?

432

Je voudrais extraire tous les nombres contenus dans une chaîne. Quelle est la mieux adaptée à la fin, aux expressions régulières ou à laisdigit() méthode?

Exemple:

line = "hello 12 hi 89"

Résultat:

[12, 89]
pablouche
la source

Réponses:

485

Si vous souhaitez uniquement extraire uniquement des entiers positifs, essayez ce qui suit:

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

Je dirais que c'est mieux que l'exemple regex pour trois raisons. Tout d'abord, vous n'avez pas besoin d'un autre module; deuxièmement, il est plus lisible car vous n'avez pas besoin d'analyser le mini-langage regex; et troisièmement, il est plus rapide (et donc probablement plus pythonique):

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

Cela ne reconnaîtra pas les flottants, les entiers négatifs ou les entiers au format hexadécimal. Si vous ne pouvez pas accepter ces limitations, la réponse de slim ci - dessous fera l'affaire.

fmark
la source
5
cela échouera pour un cas comme "h3110 23 chat 444.4 lapin 11-2 chien"
sharafjaffri
8
Le cas normatif utilise re. C'est un outil général et puissant (vous apprenez donc quelque chose de très utile). La vitesse est quelque peu hors de propos dans l'analyse des journaux (ce n'est pas un solveur numérique intensif après tout), le remodule est dans la bibliothèque Python standard et cela ne fait pas de mal de le charger.
Ioannis Filippidis
19
J'avais des cordes comme mumblejumble45mumblejumbledans lesquelles je savais qu'il n'y avait qu'un seul numéro. La solution est simple int(filter(str.isdigit, your_string)).
Jonas Lindeløv
1
Un petit commentaire: vous définissez la variable strqui remplace alors l' strobjet et la méthode en python de base. Ce n'est pas une bonne pratique car vous en aurez peut-être besoin plus tard dans le script.
Jonas Lindeløv
11
int(filter(...))augmentera TypeError: int() argument must be a string...pour Python 3.5, vous pouvez donc utiliser la version mise à jour: int(''.join(filter(str.isdigit, your_string)))pour extraire tous les chiffres en un seul entier.
Mark Mishyn
449

J'utiliserais une expression rationnelle:

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

Cela correspondrait également à 42 de bla42bla. Si vous souhaitez uniquement des nombres délimités par des limites de mots (espace, point, virgule), vous pouvez utiliser \ b:

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

Pour finir avec une liste de nombres au lieu d'une liste de chaînes:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
Vincent Savard
la source
9
... puis mappez intdessus et vous avez terminé. +1 surtout pour la dernière partie. Je suggère cependant des chaînes brutes ( r'\b\d+\b' == '\\b\\d+\\b').
5
Il pourrait être mis dans une liste avec un générateur, tel que:int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
GreenMatt
7
@GreenMatt: c'est techniquement une compréhension de liste (pas un générateur), mais je conviens que les compréhensions / générateurs sont plus Pythonic que map.
Seth Johnson
1
@Seth Johnson: Oups! Vous avez raison, j'ai mal saisi ce qui était apparemment un état d'esprit embué. :-( Merci pour la correction!
GreenMatt
2
J'ai un problème cependant. Et si je veux extraire des nombres flottants comme 1.45 dans "hello1.45 hi". Cela me donnera 1 et 45 comme deux nombres différents
ab123
89

C'est plus qu'un peu en retard, mais vous pouvez également étendre l'expression régulière pour tenir compte de la notation scientifique.

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)

Donne tout bon!

En outre, vous pouvez consulter l' expression régulière intégrée AWS Glue

aidan.plenert.macdonald
la source
1
Comme c'est la seule réponse que quelqu'un aime, voici comment le faire avec la notation scientifique "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". Ou une certaine variation. S'amuser!
aidan.plenert.macdonald
Trouvez qu'il y a un problème avec le cas le plus simple, par exemple, s = "4"ne renvoie aucune correspondance. Peut-il être modifié pour prendre également en charge cela?
batFINGER
1
sympa mais il ne gère pas les virgules (par exemple 74,600)
yekta
Un groupe plus verbeux est [+-]?\d*[\.]?\d*(?:(?:[eE])[+-]?\d+)?Ce groupe donne des faux positifs (c'est-à +- dire qu'il est parfois capturé par lui-même), mais est capable de gérer plus de formulaires, comme .001, plus il ne combine pas automatiquement les nombres (comme dans s=2+1)
DavisDude
24
Ah oui, l'évidence [-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?- si stupide de moi ... comment pourrais-je ne pas penser à cela?
Przemek D du
70

Je suppose que vous voulez des flottants et pas seulement des entiers, alors je ferais quelque chose comme ceci:

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

Notez que certaines des autres solutions publiées ici ne fonctionnent pas avec des nombres négatifs:

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

>>> '-3'.isdigit()
False
jmnas
la source
Cela trouve des flottants et des entiers positifs et négatifs. Pour les entiers uniquement positifs et négatifs, passez floatà int.
Hugo
3
Pour les nombres négatifs:re.findall("[-\d]+", "1 -2")
ytpillai
Est-ce que cela fait une différence si nous écrivons continueau lieu de passdans la boucle?
D. Jones
Cela attrape plus que des entiers positifs, mais l'utilisation de split () manquera des nombres qui ont des symboles monétaires précédant le premier chiffre sans espace, ce qui est courant dans les documents financiers
Marc Maxmeister
Ne fonctionne pas pour les flotteurs qui n'ont pas d'espace avec d'autres personnages, par exemple: «4,5 k choses» fonctionnera, «4,5 k choses» ne fonctionnera pas.
Jay D.
64

Si vous savez qu'il n'y aura qu'un seul numéro dans la chaîne, c'est-à-dire «bonjour 12 salut», vous pouvez essayer de filtrer.

Par exemple:

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

Mais attention !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
dfostic
la source
12
En Python 3.6.3, je l'ai TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter'- réparé en utilisantint("".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
la source
3
Bienvenue à SO et merci d'avoir posté une réponse. Il est toujours recommandé d'ajouter des commentaires supplémentaires à votre réponse et pourquoi cela résout le problème, plutôt que de simplement publier un extrait de code.
se
n'a pas fonctionné dans mon cas. pas très différent de la réponse ci
oldboy
ValueError: impossible de convertir la chaîne en float: 'e' et cela ne fonctionne pas dans certains cas :(
Vilq
15

Je cherchais une solution pour supprimer les masques de chaînes, en particulier des numéros de téléphone brésiliens, ce message n'a pas répondu mais m'a inspiré. Voici ma solution:

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

Utiliser Regex ci-dessous est le moyen

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)

avec 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
la source
Vous devriez au moins compiler l'expression régulière si vous n'utilisez pasfindall()
information_interchange
2
repl_str = re.compile('\d+.?\d*') devrait être: repl_str = re.compile('\d+\.?\d*') Pour un exemple reproductible en utilisant 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)

Salut ,

vous pouvez rechercher tous les entiers de la chaîne à travers chiffre en utilisant l'expression findall.

Dans la deuxième étape, créez une liste res2 et ajoutez les chiffres trouvés dans la chaîne à cette liste

J'espère que cela t'aides

Cordialement, Diwakar Sharma

Diwakar SHARMA
la source
La réponse fournie a été signalée pour examen en tant que publication de faible qualité. Voici quelques lignes directrices pour Comment écrire une bonne réponse? . Cette réponse fournie peut être correcte, mais elle pourrait bénéficier d'une explication. Les réponses codées uniquement ne sont pas considérées comme de «bonnes» réponses. De l' examen .
Trenton McKinney
solution simple et fonctionnelle, appréciée
moyo
7

Cette réponse contient également le cas où le nombre est flottant dans la chaîne

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
la source
5

Je suis étonné de voir que personne n’a encore mentionné l’utilisation de itertools.groupby comme alternative pour y parvenir.

Vous pouvez utiliser itertools.groupby()avec str.isdigit()pour extraire des nombres de la chaîne comme:

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]

La valeur détenue par lsera:

[12, 89]

PS: Ceci est juste à des fins d'illustration pour montrer que nous pouvons également utiliser une alternative groupbypour y parvenir. Mais ce n'est pas une solution recommandée. Si vous voulez y parvenir, vous devez utiliser la réponse acceptée de fmark basée sur l'utilisation de la compréhension de liste avec str.isdigitcomme filtre.

Moinuddin Quadri
la source
4

J'ajoute simplement cette réponse parce que personne n'en a ajouté une en utilisant la gestion des exceptions et parce que cela fonctionne également pour les flottants

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

Production :

[1234.0, 56.78]
Raghav
la source
4

Pour capturer différents modèles, il est utile d'interroger avec différents modèles.

Configurez tous les modèles qui captent différents modèles de nombres d'intérêt:

(trouve les virgules) 12 300 ou 12 300,00

«[\ d] + [., \ d] +»

(trouve des flotteurs) 0,123 ou 0,123

«[\ d] * [.] [\ d] +»

(trouve des entiers) 123

«[\ d] +»

Combinez avec le tuyau (|) en un seul motif avec plusieurs ou conditionnels .

(Remarque: Mettez les modèles complexes en premier sinon les modèles simples renverront des morceaux de la capture complexe au lieu de la capture complexe renvoyant la capture complète).

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

Ci-dessous, nous confirmerons la présence d'un modèle re.search(), puis retournerons une liste itérable des captures. Enfin, nous imprimerons chaque capture en utilisant la notation entre crochets pour sous-sélectionner la valeur de retour de l'objet de correspondance à partir de l'objet de correspondance.

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

Retour:

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

Puisqu'aucune de celles-ci ne traitait de chiffres financiers réels dans des documents Excel et Word que je devais trouver, voici ma variation. Il gère les ints, les flottants, les nombres négatifs, les numéros de devise (car il ne répond pas en cas de fractionnement), et a la possibilité de supprimer la partie décimale et de simplement retourner les ints, ou tout renvoyer.

Il gère également le système de numérotation Indian Laks où les virgules apparaissent irrégulièrement, pas tous les 3 nombres.

Il ne gère pas la notation scientifique ou les nombres négatifs mis entre parenthèses dans les budgets - apparaîtra positif.

Il n'extrait pas non plus de dates. Il existe de meilleures façons de rechercher des dates dans des chaînes.

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
la source
1

@jmnas, j'ai aimé votre réponse, mais elle n'a pas trouvé de flotteurs. Je travaille sur un script pour analyser le code allant à une fraiseuse CNC et j'avais besoin de trouver les dimensions X et Y qui peuvent être des entiers ou des flottants, j'ai donc adapté votre code aux éléments suivants. Cela trouve int, float avec des valeurs positives et négatives. Ne trouve toujours pas de valeurs au format hexadécimal, mais vous pouvez ajouter "x" et "A" à "F" au num_chartuple et je pense que cela analyserait des choses comme '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
la source
0

La meilleure option que j'ai trouvée est ci-dessous. Il extraira un nombre et peut éliminer tout type de caractère.

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
la source
0

Pour les numéros de téléphone, vous pouvez simplement exclure tous les caractères non numériques avec \ D dans l'expression régulière:

import re

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