Index of a substring in a string with Swift
我曾经在JavaScript中这样做:
1 | var domains ="abcde".substring(0,"abcde".indexOf("cd")) // Returns"ab" |
Swift没有此功能,如何做类似的事情?
编辑/更新:
Xcode 11? Swift 5.1或更高版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | extension StringProtocol { func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? { range(of: string, options: options)?.lowerBound } func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? { range(of: string, options: options)?.upperBound } func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] { var indices: [Index] = [] var startIndex = self.startIndex while startIndex < endIndex, let range = self[startIndex...] .range(of: string, options: options) { indices.append(range.lowerBound) startIndex = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex } return indices } func ranges<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Range<Index>] { var result: [Range<Index>] = [] var startIndex = self.startIndex while startIndex < endIndex, let range = self[startIndex...] .range(of: string, options: options) { result.append(range) startIndex = range.lowerBound < range.upperBound ? range.upperBound : index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex } return result } } |
用法:
1 2 3 4 5 6 7 | let str ="abcde" if let index = str.index(of:"cd") { let substring = str[..<index] // ab let string = String(substring) print(string) //"ab " } |
1 2 3 4 5 | let str ="Hello, playground, playground, playground" str.index(of:"play") // 7 str.endIndex(of:"play") // 11 str.indices(of:"play") // [7, 19, 31] str.ranges(of:"play") // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}] |
不区分大小写的样本
1 2 3 4 | let query ="Play" let ranges = str.ranges(of: query, options: .caseInsensitive) let matches = ranges.map { str[$0] } // print(matches) // ["play","play","play"] |
正则表达式样本
1 2 3 4 5 6 7 8 | let query ="play" let escapedQuery = NSRegularExpression.escapedPattern(for: query) let pattern ="\\b\(escapedQuery)\\w+" // matches any word that starts with"play" prefix let ranges = str.ranges(of: pattern, options: .regularExpression) let matches = ranges.map { str[$0] } print(matches) // ["playground","playground","playground"] |
使用
1 2 3 4 5 6 7 8 | let str ="abcde" if let range = str.range(of:"cd") { let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound] print(substring) // Prints ab } else { print("String not present") } |
如果您没有定义此操作符
在Swift 4中:
获取字符串中字符的索引:
1 2 3 4 | let str ="abcdefghabcd" if let index = str.index(of:"b") { print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1)) } |
使用Swift 4从String创建SubString(前缀和后缀):
1 2 3 4 5 6 7 | let str : String ="ilike" for i in 0...str.count { let index = str.index(str.startIndex, offsetBy: i) // String.Index let prefix = str[..<index] // String.SubSequence let suffix = str[index...] // String.SubSequence print("prefix \(prefix), suffix : \(suffix)") } |
输出量
1 2 3 4 5 6 | prefix , suffix : ilike prefix i, suffix : like prefix il, suffix : ike prefix ili, suffix : ke prefix ilik, suffix : e prefix ilike, suffix : |
如果要在两个索引之间生成子字符串,请使用:
1 2 | let substring1 = string[startIndex...endIndex] // including endIndex let subString2 = string[startIndex..<endIndex] // excluding endIndex |
在Swift中可以执行此操作,但是它需要花费更多的行,这是一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | func indexOf(source: String, substring: String) -> Int? { let maxIndex = source.characters.count - substring.characters.count for index in 0...maxIndex { let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count) if source.substringWithRange(rangeSubstring) == substring { return index } } return nil } var str ="abcde" if let indexOfCD = indexOf(str, substring:"cd") { let distance = str.startIndex.advancedBy(indexOfCD) print(str.substringToIndex(distance)) // Returns"ab" } |
此功能尚未优化,但可以处理短字符串。
在Swift版本3中,String没有类似的功能-
1 | str.index(of: String) |
如果子字符串需要索引,则获取范围的方法之一是。在返回范围的字符串中,我们具有以下函数-
1 2 3 | str.range(of: <String>) str.rangeOfCharacter(from: <CharacterSet>) str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>) |
例如,在str中查找游戏的首次出现的索引
1 2 3 4 | var str ="play play play" var range = str.range(of:"play") range?.lowerBound //Result : 0 range?.upperBound //Result : 4 |
注意:范围是可选的。如果找不到字符串,它将为零。例如
1 2 3 4 | var str ="play play play" var range = str.range(of:"zoo") //Result : nil range?.lowerBound //Result : nil range?.upperBound //Result : nil |
Leo Dabus的答案很好。这是我基于他使用
迅捷5.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | extension StringProtocol { func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] { let result: [Range<String.Index>] = self.indices.compactMap { startIndex in let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale) } return result } } // Usage let str ="Hello, playground, playground, playground" let ranges = str.ranges(of:"play") ranges.forEach { print("[\($0.lowerBound.utf16Offset(in: str)), \($0.upperBound.utf16Offset(in: str))]") } // result - [7, 11], [19, 23], [31, 35] |
您是否考虑过使用NSRange?
1 2 3 | if let range = mainString.range(of: mySubString) { //... } |
这里有三个密切相关的问题:
-
在Cocoa NSString世界(基础)中,所有子字符串查找方法均已结束。
-
Foundation NSRange与Swift Range不匹配;前者使用开始和长度,后者使用端点
-
通常,Swift字符是使用
String.Index 而不是Int进行索引的,而Foundation字符是使用Int进行索引的,并且它们之间没有简单的直接转换(因为Foundation和Swift对组成字符的想法不同)
考虑到所有这些,让我们考虑如何编写:
1 2 3 | func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? { // ? } |
必须使用String Foundation方法在
但是我们不必!我们要做的就是使用采用
1 2 3 4 5 6 | func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? { guard let r = s.range(of:s2) else {return nil} var s = s.prefix(upTo:r.lowerBound) s = s.dropFirst(from) return s } |
或者,如果您希望能够将此方法直接应用于字符串,例如...
1 | let output ="abcde".substring(from:0, toSubstring:"cd") |
...然后将其扩展为String:
1 2 3 4 5 6 7 8 | extension String { func substring(from:Int, toSubstring s2 : String) -> Substring? { guard let r = self.range(of:s2) else {return nil} var s = self.prefix(upTo:r.lowerBound) s = s.dropFirst(from) return s } } |
迅捷5
查找子串的索引
1 2 3 4 5 6 7 8 | let str ="abcdecd" if let range: Range<String.Index> = str.range(of:"cd") { let index: Int = str.distance(from: str.startIndex, to: range.lowerBound) print("index:", index) //index: 2 } else { print("substring not found") } |
查找字符索引
1 2 3 4 5 6 7 8 | let str ="abcdecd" if let firstIndex = str.firstIndex(of:"c") { let index = str.distance(from: str.startIndex, to: firstIndex) print("index:", index) //index: 2 } else { print("symbol not found") } |