我不熟悉 String.Index,有没有比这更好的方法来制作子字符串键:
let a = "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA"
let keysize = 2
let size = a.count + 1 - keysize
var counts: [String: Int] = [:]
var i = 0
while i < size {
let start_offset = a.index(seq.startIndex, offsetBy: i)
let end = a.index(start_offset, offsetBy: keysize)
if let key = String( a[start_offset..<end] ) {
if let v = counts[key] {
counts[key] = v + 1
} else {
counts[key] = 1
}
}
i += 1
}
for (k,v) in counts {
print("\(k): \(v)")
}
结果:
CC: 5
TA: 1
TG: 3
GC: 9
CG: 7
GT: 2
GA: 3
CA: 3
AC: 2
TC: 2
AG: 3
AT: 1
TT: 2
GG: 12
AA: 1
CT: 3
10
最佳答案
4
你可以尝试使用这个。在 Xcode 16.0 中测试
let seq = "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA"
let keysize = 2
var counts: [String: Int] = [:]
for i in seq.indices.dropLast(keysize - 1) {
let key = String(seq[i..<seq.index(i, offsetBy: keysize)])
counts[key, default: 0] += 1
}
2
-
非常优雅的解决方案。(已投票)。我喜欢使用
indices.dropLast
、 和counts[key, default: 0] += 1
。当它首次添加到语言中时,我读到了它,但没有使用过它,所以忘记了它。
– -
此版本为 238.01s,utf8 版本为 174.10s。
–
|
官方的包可以简化这一工作,使用:
import Algorithms
let counts = seq
.windows(ofCount: 2)
.reduce(into: [:]) { counts, window in
counts[window, default: 0] += 1
}
counts
.sorted { $0.value > $1.value } // Optional: sort before printing
.forEach { k, v in print("\(k): \(v)") }
7
-
有趣。@Sweeper 有点暗示但又有点没有?
– -
错误:没有这样的模块“算法”
– -
1您需要将其添加为依赖项: … 请参阅
– -
考虑到具体问题及其规模,我预计这将极大地受益于将文件读取为
Data
而不是构造字符串。Alexander 的代码对于数据的工作方式相同,除了位print
,您需要使用 将其重新转换为字符串String(decoding: k, as: UTF8.self)
。在您的情形下,您可能能够避免制作许多字符串,这很好。避免这种情况可能会大大提高性能,特别是如果您使用 读取文件Data(contentsOf: url, options: [.mappedIfSafe])
,这可以避免将文件一次性加载到内存中。
–
-
@igouy 阅读我链接的包的说明。它显示了如何将其添加到您的项目中。此外,如果性能是一个问题,Rob 建议直接使用数据,这很好。
–
|
在内部,String 对象可以以不同的编码保存其数据。例如,UTF8 使用可变数量的字节来存储每个字形。因此,按索引获取字形的成本相对较高。String.Index
使您能够编写快速高效地遍历字符串的代码,但从第一个字形(使用index(:offsetBy:)
)开始索引,并使用计数到末尾的索引,O(n)
每次调用都有时间复杂度。因此,您的代码将具有 ≈ O(n^2)
(又称“n 平方”)时间复杂度。对于短字符串,这不是什么大问题,但如果您尝试将其应用于较长的字符串,其性能将变得非常糟糕。
您应该尝试重写它以使用基于前一个索引的 String.Index。或者,您可以将字符串转换为字符数组,并使用整数索引对其进行索引。这很快,但会占用更多内存。
将字符串转换为数组的方法Character
可能如下所示:
import Foundation
let seq = "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA"
let array = Array(seq)
let pairs = NSCountedSet()
for index in 0..<array.count-1 {
let pair = "\(array[index])\(array[index+1])"
pairs.add(pair)
}
pairs.forEach { print($0, pairs.count(for: $0))}
(NSCountedSet
似乎比字典更适合您的应用程序,尽管您当然可以使用字典。)
我编写了一些代码,使用字符数组将您的方法与我的方法进行比较:
import Foundation
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let alphaArray = Array(alphabet)
var seq = ""
var counts: [String: Int] = [:]
func addPair(aPair: String) {
if let v = counts[aPair] {
counts[aPair] = v + 1
} else {
counts[aPair] = 1
}
}
for _ in 0...100_000 {
seq.append(alphaArray[Int.random(in:0..<alphaArray.count)] )
}
var start = Date()
let array = Array(seq)
let pairs = NSCountedSet()
for index in 0..<array.count-1 {
let pair = "\(array[index])\(array[index+1])"
pairs.add(pair)
}
let elapsed1 = -start.timeIntervalSinceNow
print(elapsed1)
start = Date()
let keysize = 2
let lasti = seq.count - keysize
counts = [:]
for i in 0...lasti {
let ii = seq.index(seq.startIndex, offsetBy: i)
let jj = seq.index(ii, offsetBy: keysize)
let key = String( seq[ii..<jj] )
pairs.add(key)
}
let elapsed2 = -start.timeIntervalSinceNow
print(elapsed2)
print("Array processing is \(elapsed2/elapsed1) times faster")
输出为
0.03937792778015137
25.516054034233093
Array processing is 647.9785878193057 times faster
因此,对于 100_000 个字符,基于数组的方法几乎快 650 倍。对于较大的字符串,从起始位置偏移的方法会变得非常非常慢。(对于大型数据集,N 平方的性能会很快下降。)
12
-
>您应该尝试重写它以使用基于前一个索引的 String.Index。<是的,我需要一个示例来说明如何做到这一点。
– -
我使用数组添加了一个答案。请参阅 Kendar Sukerkar 关于使用索引的代码的出色答案。
– -
对于 250MB 长的字符串,您的解决方案很可能无法在您的一生中完成。(对于大型数据集,n 平方时间性能非常差。对于 250MB,这大约是68,719,476,736,000,000 次操作。)
– -
我对原来的程序做了一些小的调整,但似乎性能发生了根本性的改变。
–
-
Kedar Sukerkar 的答案可能是最好的。它应该表现得非常好,并且可以处理您需要的任何块大小。
–
|
.utf8
性能更佳(250MB输入)。
let seq = "GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA"
let a = seq.utf8
let keysize = 2
let size = a.count + 1 - keysize
var counts: [String: Int] = [:]
var i = 0
while i < size {
let start_offset = a.index(seq.startIndex, offsetBy: i)
let end = a.index(start_offset, offsetBy: keysize)
if let key = String( a[start_offset..<end] ) {
if let v = counts[key] {
counts[key] = v + 1
} else {
counts[key] = 1
}
}
i += 1
}
我必须调整一些方法参数String.UTF8View
14
-
utf8 仅适用于扩展 ASCII 字符集中的字符。如果您使用 UTF8 中占用多个字节的字符,您的代码将完全混乱。(重音字符、希腊字母或其他非罗马字母等。尝试将“AÄBÇñüÉÈ”作为字符串,仅作为示例。
– -
将字符串转换为 utf16 会对更大组字符产生良好的结果。(想想需要超过 16 位的汉字字符仍然会使其混淆。
– -
1
– -
@igouy 我很惊讶这里的性能差异如此之大。如果我没记错的话,
String
对像这样的 ascii 字符串进行了优化,这使得索引比一般的 unicode 情况快得多。你确定你是用优化的(发布)版本测试的吗?
– -
此外,如果该
utf8
技巧确实有助于提高性能,您可以非常轻松地将其应用于我的答案。您只需添加.utf8
和.map { String($0)! }
阶段,如下所示:seq.utf8.windows(ofCount: 2).map { String($0)! }.reduce(into: [:]) { counts, window in counts[window, default: 0] += 1 }
。
–
|
.chunks(of: 2)
Apple 的 swift-algorithms–
–
windows(ofCount: 2)
,而不是chunks
。–
–
.chunks(of:)
行不通。原帖作者想要一串"123456"
来生成["12", "23", "45", "56"]
。.chunks(of:2)
只会生成["12", "34", "56"]
–
|