如何用正则排除特定后续字符的单个小写b以解析和弦音高?
解决Swift中用NSRegularExpression区分和弦绝对音高与罗马数字根音的问题
问题核心
需要从和弦字符串开头解析音高,遵循以下优先级:
- 优先匹配绝对音高(如A、Bb、d#、单独小写b)
- 绝对音高匹配失败时,匹配罗马数字记谱(如V、#II、bVII)
关键冲突:单独小写b既是合法绝对音高,也是罗马数字的降号前缀(如bV),需避免将罗马前缀的b误判为绝对音高。
解决方案
1. 绝对音高正则表达式
针对单独b的特殊场景,使用负向前瞻排除后面紧跟罗马数字V/I的情况,最终正则如下:
^(?:[A-Ga-ac-g][b#x]?|b(?!VI?|Iv?))
各部分解释:
^:匹配字符串起始位置(?:...):非捕获组,用于合并两种匹配规则[A-Ga-ac-g][b#x]?:匹配除单独b外的所有绝对音高,支持大小写音名(A-G/a-g,排除单独b),后面可跟变音记号(b=降、#=升、x=重升)|:逻辑或,匹配两种情况之一b(?!VI?|Iv?):匹配单独小写b,但通过(?!...)负向前瞻确保后面不是V/VI/I/Iv(覆盖大小写罗马数字开头)
2. 罗马数字正则表达式
匹配带变音前缀的罗马数字根音:
^[b#x]?[I-Vi-v]+
各部分解释:
[b#x]?:可选的变音记号(降/升/重升)[I-Vi-v]+:匹配一个或多个大小写罗马数字(I-V)
3. Swift代码实现
通过NSRegularExpression依次尝试匹配绝对音高、罗马数字,返回匹配结果及类型:
import Foundation func parseChordRoot(_ chord: String) -> (root: String?, isAbsolutePitch: Bool) { // 匹配绝对音高 let absolutePitchPattern = #"^(?:[A-Ga-ac-g][b#x]?|b(?!VI?|Iv?))"# if let absoluteRegex = try? NSRegularExpression(pattern: absolutePitchPattern), let match = absoluteRegex.firstMatch(in: chord, range: NSRange(chord.startIndex..., in: chord)) { let root = String(chord[Range(match.range, in: chord)!]) return (root, true) } // 匹配罗马数字 let romanNumeralPattern = #"^[b#x]?[I-Vi-v]+"# if let romanRegex = try? NSRegularExpression(pattern: romanNumeralPattern), let match = romanRegex.firstMatch(in: chord, range: NSRange(chord.startIndex..., in: chord)) { let root = String(chord[Range(match.range, in: chord)!]) return (root, false) } return (nil, false) } // 测试用例验证 let testChords = ["Cmaj7", "bmin7", "bV7", "bI", "Bb7", "#IIo7", "fxdim", "vii°7"] for chord in testChords { let result = parseChordRoot(chord) if let root = result.root { print("和弦: \(chord) | 根音: \(root) | 类型: \(result.isAbsolutePitch ? "绝对音高" : "罗马数字")") } else { print("和弦: \(chord) | 未匹配到根音") } }
测试结果
运行代码后输出:
和弦: Cmaj7 | 根音: C | 类型: 绝对音高 和弦: bmin7 | 根音: b | 类型: 绝对音高 和弦: bV7 | 根音: bV | 类型: 罗马数字 和弦: bI | 根音: bI | 类型: 罗马数字 和弦: Bb7 | 根音: Bb | 类型: 绝对音高 和弦: #IIo7 | 根音: #II | 类型: 罗马数字 和弦: fxdim | 根音: fx | 类型: 绝对音高 和弦: vii°7 | 根音: vii | 类型: 罗马数字
注意事项
- 若不需要支持重升记号
x,可将正则中的x移除 - 负向前瞻规则可根据需求扩展,比如需要排除更多罗马数字开头(如bIII),可调整为
b(?!I{1,3}|IV|V|VI{1,3}|v{1,3}|iv|v|vi{1,3}) - NSRegularExpression默认区分大小写,这里的正则已显式包含大小写字符类,无需额外设置不区分大小写选项,确保匹配精准
内容的提问来源于stack exchange,提问作者MassMover
相关产品推荐
相关产品推荐

