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

string1 = 'Height_A_B132width_top100.0lengthsimple0.00001'

Я хотел бы получить переменные в словарь:

# desired output: dict1 = {'Height_A_B': 132, 'width_top': 100.0, 'lengthsimple': 0.00001}

Попытка следующего метода itertools

Input1 :

from itertools import groupby
[''.join(g) for _, g in groupby(string1, str.isdigit)]

Output1 :

['Height_A_B', '132', 'width_top', '100', '.', '0', 'lengthsimple', '0', '.', '00001']

Следующее должно почти получиться, но интерпретатор iPython говорит мне, что этот атрибут str не существует (он есть в документации). Тем не мение...

Input2 :

[''.join(g) for _, g in groupby(string1, str.isnumeric)]

Выход2 :

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-25-cf931a137f50> in <module>()
----> 1 [''.join(g) for _, g in groupby(string1, str.isnumeric)]

AttributeError: type object 'str' has no attribute 'isnumeric'

В любом случае, что произойдет, если число содержит показатель степени с символом «+» или «-»?

string2 = 'Height_A132width_top100.0lengthsimple1.34e+003'
# desired output: dict2 = {'Height_A_B': 132, 'width_top': 100.0, 'lengthsimple': 1.34e+003}

Данные 3 :

[''.join(g) for _, g in groupby(string2, str.isdigit)]

Output3 :

['Height_A', '132', 'width_top', '100', '.', '0', 'lengthsimple', '1', '.', '34', 'e+', '003']

Интересно, есть ли у кого-нибудь элегантное решение?

< Сильный > UPDATE : Ниже обсуждается вопрос о сохранении типов числовых переменных (например, int, float и т. Д.). На самом деле научная запись в string2 оказалась чем-то вроде красной сельди, потому что если вы создадите переменную

>>> a = 1.34e+003

Вы получаете

>>> print a
1340.0

В любом случае, вероятность получения струны с 1,34 + 003 в ней невелика.

Таким образом, string2 является более подходящим тестовым примером, если мы изменим его, скажем,

string2 = 'Height_A132width_top100.0lengthsimple1.34e+99'
0
feedMe 17 Дек 2015 в 16:51

4 ответа

Лучший ответ

Вы можете использовать регулярное выражение: ([^\d.]+)(\d[\d.e+-]*):

  1. [^\d.] означает: все, кроме цифр и точки
  2. + означает один или несколько.
  3. другой группе нужна хотя бы одна цифра, затем номер или e или - / +.

Группа 1 - это ключ, группа 2 - это значение.

демо

Код:

import re
vals = { x:float(y) if '.' in y else int(y) for (x,y) in (re.findall(r'([^\d.]+)(\d[\d.e+-]*)',string2))} 

{'width_top': 100.0, 'Height_A': 132, 'lengthsimple': 1340.0}
2
Ali Nikneshan 17 Дек 2015 в 17:32

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

import re

def parse_numstr(s):
    ''' Convert a numeric string to a number. 
    Return an integer if the string is a valid representation of an integer,
    Otherwise return a float, if its's a valid rep of a float,
    Otherwise, return the original string '''
    try:
        return int(s)
    except ValueError:

        try:
            return float(s)
        except ValueError:
            return s

pat = re.compile(r'([A-Z_]+)([-+]?[0-9.]+(?:e[-+]?[0-9]+)?)', re.I)

def extract(s):
    return dict((k, parse_numstr(v)) for k,v in pat.findall(s))

data = [
    'Height_A_B132width_top100.0lengthsimple0.00001',
    'Height_A132width_top100lengthsimple1.34e+003',
    'test_c4.2E1p-3q+5z123E-2e2.71828',
]

for s in data:
    print(extract(s))

вывод

{'Height_A_B': 132, 'width_top': 100.0, 'lengthsimple': 1.0000000000000001e-05}
{'width_top': 100, 'Height_A': 132, 'lengthsimple': 1340.0}
{'q': 5, 'p': -3, 'z': 1.23, 'test_c': 42.0, 'e': 2.71828}

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

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

pat = re.compile(r'([A-Z_]+)([-+]?[0-9]*\.?[0-9]*(?:e[-+]?[0-9]+)?)', re.I)

См. Также этот ответ для регулярного выражения, которое фиксирует числа в научной нотации.

1
Community 23 Май 2017 в 11:52

Это простое регулярное выражение будет работать:

[0-9.+e]+|\D+

Для создания ваших воздуховодов:

def pairs(s):
    mtch = re.finditer("[0-9.+e]+|\D+", s)
    m1, m2 = next(mtch, ""), next(mtch, "")
    while m1:
        yield m1.group(), float(m2.group())
        m1, m2 = next(mtch, ""), next(mtch, "")

Демо:

In [27]: s =  'Height_A_B132width_top100.0lengthsimple0.00001'

In [28]: print(dict(pairs(s)))
{'Height_A_B': 132.0, 'width_top': 100.0, 'lengthsimple': 1e-05}

In [29]: s = 'Height_A132width_top100.0lengthsimple1.34e+003'

In [30]: print(dict(pairs(s)))
{'width_top': 100.0, 'Height_A': 132.0, 'lengthsimple': 1340.0}

Или для более общего подхода вы можете использовать ast.literal_eval для анализа значений для работы с несколькими типами:

from ast import literal_eval
def pairs(s):
    mtch = re.finditer("[0-9.+e]+|\D+", s)
    m1, m2 = next(mtch, ""), next(mtch, "")
    while m1:
        yield m1.group(), literal_eval(m2.group())
        m1, m2 = next(mtch, ""), next(mtch, "")

Что, если вы действительно беспокоитесь о ints vs float:

In [31]: s = 'Height_A132width_top100.0lengthsimple1.34e+99'

In [32]: dict(pairs(s))
Out[32]: {'Height_A': 132, 'lengthsimple': 1.34e+99, 'width_top': 100.0}
0
Padraic Cunningham 17 Дек 2015 в 19:32

Ну вот:

import re
p = re.compile(ur'([a-zA-z]+)([0-9.]+)')
test_str = u"Height_A_B132width_top100.0lengthsimple0.00001"

print dict(re.findall(p, test_str))
0
masnun 17 Дек 2015 в 14:03