为了同时学习一些 SwiftUI 和 Regex 的知识,我认为将 VTT 文件转换为 SRT 文件是一项非常简单的任务。在此转换中,包含句点的时间戳将转换为带有逗号的相同时间戳。
我以为使用捕获时间戳的正则表达式可以很好地处理这个问题。我编写了代码,当针对一些记录样本进行测试时,一切似乎都运行良好。但是,当我将整个 VTT 文件转储为字符串并尝试时,该函数就会挂起。我认为问题在于替换的数量。
一部典型的好莱坞电影的 VTT 文件大约有 1,500 条记录,每条记录都有时间戳,需要 2 次翻译。时间戳如下所示:00:00:48.757 --> 00:00:52.970
& 唯一需要做的就是将句号变成逗号,如下所示:00:00:48,757 --> 00:00:52,970
。
我不能只进行全局替换,因为其他句号会变成逗号,而我只想对时间戳进行替换。以下代码可以正确完成翻译,但对于大文件会挂起:
func VTT2SRT() {
var vtt$ = vttText // preserve vttText from views
let targetPattern = /(?<timeStamp>\d\d:\d\d:\d\d)./
let matchArray = vtt$.matches(of: targetPattern)
for s$ in matchArray {
vtt$ = vtt$.replacing(s$.0, with: s$.timeStamp + ",")
}
[other stuff that works just fine]
srtText = srt$ // pass it back to views
}
我不明白为什么这会挂起或需要这么长时间。有人能给我指出正确的方向或建议另一种方法吗?注意:我正在 macOS 14.6.1 (23G93) 下尝试使用 Xcode 版本 15.4 (15F31d)
一些观察:
.
正则表达式中的未转义字符将匹配任何字符(包括逗号!)。如果.
在 中使用反斜杠转义targetPattern
,则只会匹配句点字符:如果不转义
.
,您的替换字符串将与搜索字符串匹配。现在,您的示例不是重复搜索,但如果是,如果您未能转义 ,则可能会陷入无限循环.
。您的代码
replacing
在for
循环内调用。这意味着对于for
循环的每次迭代,它都会重新扫描整个字符串以查找匹配项,从而产生类似于 O(n²) 的性能。最重要的是,它会在每次迭代时创建完整字符串的新副本,这非常低效。您无需重新扫描
for
循环内的字符串,只需替换匹配的子字符串即可:通过调用
replaceSubrange
而不是replacing
,这将避免在循环的每次迭代中重新扫描字符串for
。(请注意,如果您对我为什么反转匹配数组感兴趣,这是一种强大的方法,可以确保替换某些子字符串不会使字符串中稍后找到的匹配项的索引无效。在这种情况下,这不是必需的(因为替换的长度始终与被替换的字符串相同),但这是一般模式。)
上面的代码片段将具有更高的性能:您将享受 O(n) 性能。对于短字符串,差异会很小,但对于非常长的字符串,这将非常显著。
更好的是,完全
replacing(_:maxReplacements:with:)
消除了循环的需要for
。它可以在一次 API 调用中替换所有匹配项:for
不需要循环。?<=
除了使用命名捕获组,您还可以考虑对字符串部分使用后视断言hh:mm:ss
。您实际上根本不需要替换该部分(因此您实际上不需要捕获它;您只需替换字符串.
前面的字符hh:mm:ss
):似乎较新的版本
Regex
尚不支持它,但旧版正则表达式 API 却支持,如上所示。 合乎逻辑的是,只替换有问题的单个字符,即.
,而不是整个时间戳字符串。