如何在Python中将字符串转换为标记列表?

3
当我输入以下内容时:
>>>tokenize('<[2{12.5 6.0}](3 -4 5)>')

我想要得到这个:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

基本上,我应该如何保持输入将所有内容转换为列表,同时保持数字的原始值。


你正在寻找Python中的词法分析工具吗?你可以尝试使用PLY http://www.dabeaz.com/ply/。 - neuront
将字符串拆分为单个字符列表。遍历列表一次,将数字、句点和连字符等类似数字的字符分块。再次遍历列表,将块转换为整数或浮点数。完成。 - mpenkov
我在下面给出了一个具体的解决方案,但更一般地说,可以将其视为两个任务:首先确定什么是词元,然后制作你的标记。因此,一旦你有一个与整数规则匹配的子字符串,就将其转换为整数,或者如果它与浮点数规则匹配,就将其转换为浮点数,依此类推。其余部分保持不变,所以制作你的标记只需返回子字符串即可。 - johncip
9个回答

2

您可以尝试使用标记化工具,它给出的结果与您期望的几乎相同,但对于负数如-4则略有不同,但效果也非常接近。

from StringIO import StringIO
import tokenize
str = '<[2{12.5 6.0}](3 -4 5)>'
tokens = tokenize.generate_tokens(StringIO(str).readline)
result = [x[1] for x in tokens]

这里是结果:
['[', '2', '{', '12.5', '6.0', '}', ']', '(', '3', '-', '4', '5', ')', '>', '']

不错,但令牌是特定于Python的,而且不可配置。 - Noufal Ibrahim

0

因此,为了暴力破解它,您可以使用list(your_string),但请确保随后识别应该分组在一起的内容,并在进行操作时将项目附加到元素中。

其他解决方案可能包括正则表达式、简单语法库等。并且可能更容易理解。

编辑:对于非整数#,您还可以注意到,在遇到这样的数字时,请继续突变新的、解析的令牌列表的前一个索引,当您到达下一个数字、闭合、令牌等时,您将创建一个全新的元素到列表中。

假设您将所有内容都作为字符串放入新列表中,这里是获取浮点数和整数的一种方法:

for i, e in enumerate(tokenized):
    if e.isdigit():
        tokenized[i] = int(e)
    elif '.' in e:
        tokenized[i] = float(e)
    elif '-' in e and not '.' in e:
        tokenized[i] = int(e)

最终结果是你想要的:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

此外,这个输入是否应该根据问题的输入进行建模?它可以更长吗?更高级吗?它是否总是以“<”开头,以“>”结尾? - jheld

0

一个 PLY 解决方案

tokens = (
    'LT', 'GT', 'LPAREN', 'RPAREN', 'LBRACKET', 'RBRACKET', 'LBRACE', 'RBRACE',
    'FLOAT', 'INTEGER',
)

t_LT = r'<'
t_GT = r'>'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACKET = r'\['
t_RBRACKET = r'\]'
t_LBRACE = r'{'
t_RBRACE = r'}'
t_ignore = r' '

def t_FLOAT(t):
    r'-?\d*[.]\d+'
    t.value = float(t.value)
    return t

def t_INTEGER(t):
    r'-?\d+'
    t.value = int(t.value)
    return t

def t_error(t):
    raise ValueError('invalid input')

import ply.lex as lex
lex.lex()

lex.input('<[2{12.5 6.0}](3 -4 5)>')
tokens = list(iter(lex.token, None))
for t in tokens:
    print repr(t.type), repr(t.value)
print '>', [t.value for t in tokens]

输出:

'LT' '<'
'LBRACKET' '['
'INTEGER' 2
'LBRACE' '{'
'FLOAT' 12.5
'FLOAT' 6.0
'RBRACE' '}'
'RBRACKET' ']'
'LPAREN' '('
'INTEGER' 3
'INTEGER' -4
'INTEGER' 5
'RPAREN' ')'
'GT' '>'
> ['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

您需要安装PLY。要安装它,只需:
# pip install ply

0
import re

s = '<[2{12.5 6.0}](3 -4 5)>'
p = re.compile(r"([-+]?(?:(?:\d*\.\d+)|(?:\d+\.?)))|(\S)")

conv = lambda n: float(n) if '.' in n else int(n)

[conv(m.group(1)) if m.lastindex==1 else m.group(2) for m in p.finditer(s)]

输出:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

0
import re

def tokenize(txt):

    output = []

    tokenized = re.split('([\<\>\[\]\{\}\(\)\s])',txt)

    for t in tokenized:
        if len(t.strip()) > 0:
            if re.match("^\d+?\.\d+?$",t) is None:
                if re.match("^[\d\-]\d*?$",t) is None:
                    output.append(t)
                else:
                    output.append(int(t))
            else:
                output.append(float(t))

    print(output)


tokenize('<[2{12.5 6.0}](3 -4 5)>')

输出结果为:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

0

以下是使用正则表达式的方法

import re
def tokenize(your_string):
    pattern = re.compile(r'([-+]?[0-9]*\.?[0-9]+)') # float pattern
    digital = re.compile(r'([-+]?[0-9]*$)')
    lst = []
    for item in pattern.split(your_string):
        if not item.isspace(): # remove space
            if pattern.match(item):
                if digital.match(item):
                    lst.append(int(item))
                else:
                    lst.append(float(item)) # change string to float
            else:
                lst.extend(list(item)) # make unmatched string to character list
    return lst

print tokenize('<[2{12.5 6.0}](3 -4 5)>') 

结果是:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

0

我喜欢用pyparsing来处理这种事情。

from pyparsing import Word, ZeroOrMore, oneOf, nums

def tokenize(s):
    number = Word(nums + '.' + '-')
    number.setParseAction(lambda x : eval(x[0]))
    punctuation = '< > [ ] ( ) { }'
    lexeme = number | oneOf(punctuation) | ' '
    tokenizer = ZeroOrMore(lexeme)

    return tokenizer.parseString(s)


print tokenize('<[2{12.5 6.0}](3 -4 5)>')

输出:

['<', '[', 2, '{', 12.5, 6.0, '}', ']', '(', 3, -4, 5, ')', '>']

与PLY一样,您可以使用pip install pyparsing进行安装(如果您没有它,您可以使用easy_install pip安装pip)。在实际使用中,您可能不希望在每次调用时创建pyparsing对象,因此它们可以是全局的等。


另外,一个提示:将oneOf(标点符号)更改为Suppress(oneOf(标点符号))会产生输出[2,12.5,6.0,3,-4,5]。 - johncip

0

re.split 可能是您想要的。在这里看到一个 类似的问题

在将字符串拆分为列表后,您可以遍历它并使用 int()float() 将数字成员转换为实际数字。


-2

这是一个在Python中非常好的解决方案

list(my_string)可以进行初始分词,但不保留您喜欢的属性 :(。

因此,如果我们想要使用一些比必要更重的机器,我们可以这样做

import re
a = '<[2{12.5 6.0}](3 -4 5)>'
tokenized = [x in re.split(r'[[!"#$%&\'()*+,\-/:;<=>?@[\\\]^_`{|}~ ]]*',a)) if x!='']
#or also
tokens = [x in re.split(r'[[!"#$%&\'()*+,\-/:;<=>?@[\\\]^_`{|}~ ]]*',a)) if x]

这可以用于任何语言/白板表达式,标点符号只是来自strings.punctuation,您可以自定义使用正则表达式分隔几乎所有可以表示的内容 - 几乎所有。

如果您在运行此操作时使用re.compile预编译表达式,则可以获得更好的优化效果。 在某些限制条件下,您还可以使用自动机进行一些操作;)并且这会带来巨大的好处。


他想保留数字的原始值。如果您使用list(),那么您也会将数字标记为数字。请尝试。 - zs2020
它确实将它们添加到一个列表中。但是,它会分开整数和小数部分。我希望12.5保持为[12.5,6.0],而不是['1','2','.','5']。 - Marshall Lyon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接