从 python 中的字符串中提取罗马数字
Extract roman numerals from string in python
我有一个字符串列表,其中包含以拉丁数字表示的和弦,如下所示:
['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7']
我想将这些字符串分成 3 个子列表,如下所示:
numerals = ['ii', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
chord_type=['min', 'min', 'maj', 'maj', 'maj', 'maj','maj', 'min', 'min', 'maj', 'maj', 'maj', 'maj','maj']
extentions=['7','7','', 'add9','add9','','7','7','7','','add9','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', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7']
numerals = [re.match('[IiVv]+', i).group(0) for i in chords]
print(numerals)
输出
['ii', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
请注意,我按原样使用了 re.match 尝试在字符串的开头应用模式 并将数字限制为您示例中的现有数字(而不是使用众所周知,即 IiVvXxLlCcDdMm
).
您可以像这样使用 .startswith
字符串方法非常简单地做到这一点:
nummerals = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
lst = ['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', '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', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
备注
- 原始
nummerals
列表必须按长度(降序)排序,以确保匹配最大可能的数字(.startswith
for 'ii7'
将同时匹配 'i'
和 'ii'
但你想要第二个)。
- 以上代码可能会引发
StopIteration
错误。如果你想防止这种情况发生,请为 next
. 提供后备值
我的解决方案使用了一个有点复杂的正则表达式,它有两个优点:
- 如果一个数字的扩展看起来像一个不可能的罗马数字的一部分,例如
IV
然后 I
,一个天真的方法会考虑数字 IVI
而我的方法只会考虑 IV
和 I
作为扩展名。
- 如果您需要用更大的数字扩展您的应用程序,这将适用于非常大的数字。
编辑: 显然对于和弦来说,数字越大可能没用,但谁知道呢?也许你会更新音乐的工作方式
我使用的正则表达式来自here。我对它做了一点修改,让它在这里工作。
import re
l = ['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', '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', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
['min', 'min', 'maj', 'maj', 'maj', 'maj', 'maj', 'min', 'min', 'maj', 'maj', 'maj', 'maj', 'maj']
['7', '7', '', 'add9', 'add9', '', 'maj7', '7', '7', '', 'add9', 'add9', '', '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', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7'])
numerals, chord_type, extensions = zip(*parsed)
print(list(numerals))
print(list(chord_type))
print(list(extensions))
我使用re
进行正则解析,肯定没问题。
我有一个字符串列表,其中包含以拉丁数字表示的和弦,如下所示:
['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7']
我想将这些字符串分成 3 个子列表,如下所示:
numerals = ['ii', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
chord_type=['min', 'min', 'maj', 'maj', 'maj', 'maj','maj', 'min', 'min', 'maj', 'maj', 'maj', 'maj','maj']
extentions=['7','7','', 'add9','add9','','7','7','7','','add9','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', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7']
numerals = [re.match('[IiVv]+', i).group(0) for i in chords]
print(numerals)
输出
['ii', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
请注意,我按原样使用了 re.match 尝试在字符串的开头应用模式 并将数字限制为您示例中的现有数字(而不是使用众所周知,即 IiVvXxLlCcDdMm
).
您可以像这样使用 .startswith
字符串方法非常简单地做到这一点:
nummerals = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
lst = ['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', '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', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
备注
- 原始
nummerals
列表必须按长度(降序)排序,以确保匹配最大可能的数字(.startswith
for'ii7'
将同时匹配'i'
和'ii'
但你想要第二个)。 - 以上代码可能会引发
StopIteration
错误。如果你想防止这种情况发生,请为next
. 提供后备值
我的解决方案使用了一个有点复杂的正则表达式,它有两个优点:
- 如果一个数字的扩展看起来像一个不可能的罗马数字的一部分,例如
IV
然后I
,一个天真的方法会考虑数字IVI
而我的方法只会考虑IV
和I
作为扩展名。 - 如果您需要用更大的数字扩展您的应用程序,这将适用于非常大的数字。
编辑: 显然对于和弦来说,数字越大可能没用,但谁知道呢?也许你会更新音乐的工作方式
我使用的正则表达式来自here。我对它做了一点修改,让它在这里工作。
import re
l = ['ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', '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', 'vi', 'V', 'IV', 'I', 'V', 'IV', 'ii', 'vi', 'V', 'IV', 'I', 'V', 'IV']
['min', 'min', 'maj', 'maj', 'maj', 'maj', 'maj', 'min', 'min', 'maj', 'maj', 'maj', 'maj', 'maj']
['7', '7', '', 'add9', 'add9', '', 'maj7', '7', '7', '', 'add9', 'add9', '', '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', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7', 'ii7', 'vi7', 'V', 'IVadd9', 'Iadd9', 'V', 'IVmaj7'])
numerals, chord_type, extensions = zip(*parsed)
print(list(numerals))
print(list(chord_type))
print(list(extensions))
我使用re
进行正则解析,肯定没问题。