问题描述
我有一个字符串列表,其中包含以拉丁数字表示的和弦,如下所示:
['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
请注意,我们不需要 M
、C
、L
、X
。
我知道我可以在 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']
注意事项
- 原始
nummerals
列表必须按长度(降序)排序,以确保匹配最大可能的数字(.startswith
的'ii7'
将匹配两个'i'
和'ii'
但你想要第二个)。 - 上面的代码可能会抛出一个
StopIteration
错误。如果您想防止这种情况发生,请为next
提供后备值。
我的解决方案使用了一个有点复杂的正则表达式,它提供了两个优点:
- 如果一个数字的扩展看起来像一个不可能的罗马数字的一部分,例如
IV
然后是I
,一个简单的方法会考虑数字IVI
而我的方法会仅考虑将IV
和I
作为扩展名。 - 如果您需要使用更大的数字扩展您的应用程序,这将适用于非常大的数字。
编辑:很明显,对于和弦来说,更大的数字可能没用,但谁知道呢?也许你会更新音乐的工作方式?
我使用的正则表达式来自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
进行正则表达式解析,这当然没问题。