Python: извлечение чисел из строки

298

Я извлечу все числа, содержащиеся в строке. Что лучше подходит для этой цели, регулярные выражения или метод isdigit()?

Пример:

line = "hello 12 hi 89"

Результат:

[12, 89]
  • 7
    Как бы здесь помогли isinstance ? type("12") - str .
Теги:
string
numbers

13 ответов

372
Лучший ответ

Если вы хотите извлечь только целые положительные числа, попробуйте следующее:

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

Я бы сказал, что это лучше, чем пример регулярного выражения по трем причинам. Во-первых, вам не нужен другой модуль; во-вторых, это более читаемо, потому что вам не нужно разбирать мини-язык регулярных выражений; и, в-третьих, он быстрее (и, следовательно, скорее, более питонический):

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

Это не будет распознавать поплавки, отрицательные целые числа или целые числа в шестнадцатеричном формате. Если вы не можете принять эти ограничения, тонкий ответ ниже будет делать трюк.

  • 10
    Очиститель: [int(s) for s in str.split() if s.isdigit()] ==> [23, 11, 2]
  • 0
    @ Крис Морган Правда. Не сравнение яблок с яблоками, хотя с регулярным выражением. Я изменил ответ, но не время.
Показать ещё 18 комментариев
340

Я бы использовал regexp:

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

Это также соответствует 42 из bla42bla. Если вам нужны только числа, ограниченные границами слов (пробел, период, запятая), вы можете использовать \b:

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

В итоге получится список чисел вместо списка строк:

>>> [int(s) for s in re.findall(r'\b\d+\b', 'he33llo 42 I\'m a 32 string 30')]
[42, 32, 30]
  • 9
    ... а затем int на карту int и все готово. +1 особенно для последней части. Я бы предложил исходные строки ( r'\b\d+\b' == '\\b\\d+\\b' ).
  • 5
    Его можно поместить в список с генератором, например: int_list = [int(s) for s in re.findall('\\d+', 'hello 12 hi 89')]
Показать ещё 8 комментариев
74

Это более чем поздно, но вы можете расширить выражение регулярного выражения, чтобы учесть и научную нотацию.

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)

Дает все хорошо!

Кроме того, вы можете ознакомиться со встроенным регулярным выражением AWS Glue.

  • 1
    Поскольку это единственный ответ, который кому-либо нравится, вот как это сделать с помощью научной нотации "[- +]? \ D + [\.]? \ D * [Ee]? \ D *". Или какой-то вариант. Повеселись!
  • 0
    Найти есть проблема с простейшим случаем, например, s = "4" возвращает совпадений. Можно ли отредактировать, чтобы позаботиться об этом?
Показать ещё 6 комментариев
60

Я предполагаю, что вы хотите плавать не только целыми числами, поэтому я бы сделал что-то вроде этого:

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

Обратите внимание, что некоторые другие решения, размещенные здесь, не работают с отрицательными номерами:

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

>>> '-3'.isdigit()
False
  • 0
    Это находит положительные и отрицательные числа с плавающей запятой и целые числа. Для только положительных и отрицательных целых чисел измените float на int .
  • 3
    Для отрицательных чисел: re.findall("[-\d]+", "1 -2")
Показать ещё 3 комментария
46

Если вы знаете, что в строке будет только одно число, то есть "привет 12 привет", вы можете попробовать фильтр.

Например:

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

Но будь осторожен !!! :

In [4]: int(''.join(filter(str.isdigit, '200 grams 5')))
Out[4]: 2005
  • 9
    В Python 3.6.3 я получил TypeError: int() argument must be a string, a bytes-like object or a number, not 'filter' - исправляя его с помощью int("".join(filter(str.isdigit, '200 grams')))
7
# 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]
  • 1
    Добро пожаловать в ТАК и спасибо за размещение ответа. Хорошей практикой всегда является добавление некоторых дополнительных комментариев к вашему ответу и почему оно решает проблему, а не просто публикация фрагмента кода.
  • 0
    не работал в моем случае. не сильно отличается от ответа выше
7

В этом ответе также содержится случай, когда число с плавающей точкой в ​​строке

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

Я искал решение для удаления строковых масок, особенно с бразильских телефонных номеров, этот пост не ответил, но вдохновил меня. Это мое решение:

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

Я поражен, увидев, что никто еще не упомянул использование itertools.groupby в качестве альтернативы для достижения этой цели.

Вы можете использовать itertools.groupby() вместе с 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]

Значение, удерживаемое l будет:

[12, 89]

PS: Это просто для иллюстрации, чтобы показать, что в качестве альтернативы мы могли бы также использовать groupby для достижения этой цели. Но это не рекомендуемое решение. Если вы хотите добиться этого, вы должны использовать принятый ответ fmark, основанный на использовании понимания списка с str.isdigit качестве фильтра.

2

Поскольку ни один из них не занимался финансовыми цифрами реального мира в документах Excel и Word, которые мне нужно было найти, вот моя вариация. Он обрабатывает ints, float, отрицательные числа, номера валют (потому что он не отвечает на split), и имеет возможность отбрасывать десятичную часть и просто возвращать ints или возвращать все.

Он также обрабатывает систему чисел индийских лаков, где запятые появляются нерегулярно, а не каждые 3 числа друг от друга.

Он не обрабатывает научную нотацию или отрицательные числа, помещенные в круглые скобки в бюджетах, - будет казаться положительным.

Он также не выделяет даты. Есть лучшие способы поиска дат в строках.

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

Использование Regex ниже - это способ

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

@jmnas, мне понравился ваш ответ, но он не нашел поплавков. Я работаю над script для анализа кода, идущего на станцию ​​с ЧПУ, и ему нужно найти как размеры X, так и Y, которые могут быть целыми числами или плавать, поэтому я адаптировал ваш код к следующему. Это находит int, float с положительными и отрицательными vals. Все еще не находит шестнадцатеричные форматированные значения, но вы можете добавить "x" и "A" через "F" в кортеж num_char, и я думаю, что он будет анализировать такие вещи, как "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)
0

Лучший вариант, который я нашел, ниже. Он будет извлекать число и может устранить любой тип char.

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)    

Ещё вопросы

Сообщество Overcoder
Наверх
Меню