从python中的字符串中提取罗马数字

问题描述

我有一个字符串列表,其中包含以拉丁数字表示的和弦,如下所示:

['ii7','vi7','V','IVadd9','Iadd9','IVmaj7','ii7','IVmaj7']

我想将这些字符串分成 3 个子列表,如下所示:

numerals = ['ii','vi','IV','I','ii','IV']
chord_type=['min','min','maj','maj']
extentions=['7','7','','add9','7']

(如您所见,大写罗马数字对应和弦类型中的“maj”,非大写字母对应“min”。)

在我的例子中所有可能的罗马数字:

i,ii,iii,iv,v,vi,vii,I,II,III,IV,V,VI,VII

请注意,我们不需要 MCLX

我知道我可以在 Python 中从字符串中的字母中提取或拆分数字,如 here 所述,但我如何提取罗马数字?

我考虑过使用诸如 match 正则表达式之类的东西,但我对如何定义这 7 个罗马数字感到困惑,因为这些字符可能会再次出现在字符串中。

解决方法

如果罗马数字总是在前面,那么你可以这样做

import re
chords = ['ii7','vi7','V','IVadd9','Iadd9','IVmaj7','ii7','IVmaj7']
numerals = [re.match('[IiVv]+',i).group(0) for i in chords]
print(numerals)

输出

['ii','vi','IV','I','ii','IV']

请注意,我使用了 re.match,因为它尝试在字符串的开头应用模式,并将有限的数字限制在您的示例中(而不是使用所有已知的即 {{ 1}}).

,

您可以非常简单地使用 .startswith 字符串方法执行此操作,如下所示:

nummerals = ['i','iii','iv','v','vii','II','III','VI','VII']
lst = ['ii7','IVmaj7']


nummerals.sort(key=len,reverse=True)  # see note 1

res = [next(n for n in nummerals if y.startswith(n)) for y in lst]  # see note 2
print(res)  # -> ['ii','IV']

注意事项

  1. 原始 nummerals 列表必须按长度(降序)排序,以确保匹配最大可能的数字(.startswith'ii7' 将匹配两个 'i''ii' 但你想要第二个)。
  2. 上面的代码可能会抛出一个 StopIteration 错误。如果您想防止这种情况发生,请为 next 提供后备值。
,

我的解决方案使用了一个有点复杂的正则表达式,它提供了两个优点:

  1. 如果一个数字的扩展看起来像一个不可能的罗马数字的一部分,例如 IV 然后是 I,一个简单的方法会考虑数字 IVI 而我的方法会仅考虑将 IVI 作为扩展名。
  2. 如果您需要使用更大的数字扩展您的应用程序,这将适用于非常大的数字。

编辑:很明显,对于和弦来说,更大的数字可能没用,但谁知道呢?也许你会更新音乐的工作方式?

我使用的正则表达式来自here。我对它做了一点修改,让它在这里工作。

import re

l = ['ii7','IVmaj7']

numerals = []
chord_type = []
extensions = []

roman_regex = '^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})'

for e in l:
    roman_search = re.search(roman_regex,e.upper())
    start = roman_search.start()
    end = roman_search.end()
    roman = e[start:end]

    numerals.append(roman)
    chord_type.append('maj' if roman[0].upper() == roman[0] else 'min')
    extensions.append(e[end:])
>>> print(numerals)
... print(chord_type)
... print(extensions)

['ii','IV']
['min','min','maj','maj']
['7','7','','add9','maj7','maj7']
,

你可以试试这个:

import re

matcher = re.compile(r'([IiVv]+)(min|maj|)(.*)')

def parse_string(s):
    gs = matcher.findall(s)[0]
    if gs[1] == '':
        gs = (gs[0],'maj' if gs[0].isupper() else 'min',gs[2])
    return gs
    
def parse_array(A):
    return [parse_string(chord) for chord in A]
    
parsed = parse_array(['ii7','IVmaj7'])

numerals,chord_type,extensions = zip(*parsed)

print(list(numerals))
print(list(chord_type))
print(list(extensions))

我使用 re 进行正则表达式解析,这当然没问题。