Related
I have the following string
let a:String = "r0bqkb0r/pppppppp/00n00n00/00000000/000P0000/0000B000/PPP0PPPP/RN0QKBNR/"
and want to convert this to PGN notation so the final result should be
result = "r1bqkb1r/pppppppp/2n2n2/8/3P4/4B3/PPP1PPPP/RN1QKBNR/"
The PGN notation converts the zeros to counts found. Normally in python, I would just use
import chess.pgn
Before deep diving into python library, is there is a succinct and 'Swift' way to do this?
Here is a solution using reduce and a separate counter
Update, rewrote it as an extension to String
extension String {
func pgpNotation() -> String {
var zeroCounter = 0
var result = self.reduce(into: "") {
if $1 == "0" {
zeroCounter += 1
return
}
if zeroCounter > 0 {
$0.append("\(zeroCounter)")
zeroCounter = 0
}
$0.append($1)
}
if zeroCounter > 0 { result.append("\(zeroCounter)")}
return result
}
}
Examples
let x = "r00d00"
print(x.pgpNotation())
let a:String = "r0bqkb0r/pppppppp/00n00n00/00000000/000P0000/0000B000/PPP0PPPP/RN0QKBNR/"
print(a.pgpNotation())
r2d2
r1bqkb1r/pppppppp/2n2n2/8/3P4/4B3/PPP1PPPP/RN1QKBNR/
There is no direct function for that but I just created a program for fun. You can check this out:-
let str = "r0bqkb0r/pppppppp/00n00n00/00000000/000P0000/0000B000/PPP0PPPP/RN0QKBNR/"
var newStr = ""
var flag = 0
// Do any additional setup after loading the view.
for char in str {
if flag == 0 {
if char == "0" {
flag += 1
}
else {
newStr.append(char)
}
}
else {
if char == "0" {
flag += 1
}
else {
newStr.append("\(flag)")
flag = 0
if char == "0" {
flag += 1
}
else {
newStr.append(char)
}
}
}
}
print(newStr)
There doesn't exist any direct method to get the pgn notation String. You can use a forEach(_:) instead, i.e.
let a = "r0bqkb0r/pppppppp/00n00n00/00000000/000P0000/0000B000/PPP0PPPP/RN0QKBNR/"
var result = ""
var count = 0
a.forEach {
if $0 == "0" {
count += 1
} else {
if count != 0 {
result.append("\(count)")
count = 0
}
result.append($0)
}
}
print(result) //r1bqkb1r/pppppppp/2n2n2/8/3P4/4B3/PPP1PPPP/RN1QKBNR/
With a simple regex and a loop (just to propose an original solution):
let a = "r0bqkb0r/pppppppp/00n00n00/00000000/000P0000/0000B000/PPP0PPPP/RN0QKBNR/"
extension String {
var chessPGN : String {
var result = self
let regex = try! NSRegularExpression(pattern: "0+")
while let match = regex.matches(in: result, range: .init(location: 0, length: result.count)).first {
if let stringRange = Range(match.range , in: result) {
result.replaceSubrange(stringRange, with: match.range.length.description)
}
}
return result
}
}
print(a.chessPGN) // r1bqkb1r/pppppppp/2n2n2/8/3P4/4B3/PPP1PPPP/RN1QKBNR/
EDIT: A version calling only once the regex
extension String {
var chessPGN : String {
var result = self
let regex = try! NSRegularExpression(pattern: "0+")
for match in regex.matches(in: result, range: .init(location: 0, length: result.count)).sorted(by: { $0.range.location > $1.range.location }) {
if let stringRange = Range(match.range , in: result) {
result.replaceSubrange(stringRange, with: match.range.length.description)
}
}
return result
}
}
I'm used to do this in JavaScript:
var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"
Swift doesn't have this function, how to do something similar?
edit/update:
Xcode 11.4 • Swift 5.2 or later
import Foundation
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] {
ranges(of: string, options: options).map(\.lowerBound)
}
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
}
}
usage:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab\n"
}
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}]
case insensitive sample
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
regular expression sample
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"]
Using String[Range<String.Index>] subscript you can get the sub string. You need starting index and last index to create the range and you can do it as below
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")
}
If you don't define the start index this operator ..< , it take the starting index. You can also use str[str.startIndex..<range.lowerBound] instead of str[..<range.lowerBound]
Swift 5
Find index of substring
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")
}
Find index of Character
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")
}
In Swift 4 :
Getting Index of a character in a string :
let str = "abcdefghabcd"
if let index = str.index(of: "b") {
print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}
Creating SubString (prefix and suffix) from String using Swift 4:
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)")
}
Output
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
If you want to generate a substring between 2 indices , use :
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
Doing this in Swift is possible but it takes more lines, here is a function indexOf() doing what is expected:
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"
}
This function is not optimized but it does the job for short strings.
There are three closely connected issues here:
All the substring-finding methods are over in the Cocoa NSString world (Foundation)
Foundation NSRange has a mismatch with Swift Range; the former uses start and length, the latter uses endpoints
In general, Swift characters are indexed using String.Index, not Int, but Foundation characters are indexed using Int, and there is no simple direct translation between them (because Foundation and Swift have different ideas of what constitutes a character)
Given all that, let's think about how to write:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
// ?
}
The substring s2 must be sought in s using a String Foundation method. The resulting range comes back to us, not as an NSRange (even though this is a Foundation method), but as a Range of String.Index (wrapped in an Optional, in case we didn't find the substring at all). However, the other number, from, is an Int. Thus we cannot form any kind of range involving them both.
But we don't have to! All we have to do is slice off the end of our original string using a method that takes a String.Index, and slice off the start of our original string using a method that takes an Int. Fortunately, such methods exist! Like this:
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
}
Or, if you prefer to be able to apply this method directly to a string, like this...
let output = "abcde".substring(from:0, toSubstring:"cd")
...then make it an extension on String:
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
}
}
Swift 5
let alphabet = "abcdefghijklmnopqrstuvwxyz"
var index: Int = 0
if let range: Range<String.Index> = alphabet.range(of: "c") {
index = alphabet.distance(from: alphabet.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
Swift 5
extension String {
enum SearchDirection {
case first, last
}
func characterIndex(of character: Character, direction: String.SearchDirection) -> Int? {
let fn = direction == .first ? firstIndex : lastIndex
if let stringIndex: String.Index = fn(character) {
let index: Int = distance(from: startIndex, to: stringIndex)
return index
} else {
return nil
}
}
}
tests:
func testFirstIndex() {
let res = ".".characterIndex(of: ".", direction: .first)
XCTAssert(res == 0)
}
func testFirstIndex1() {
let res = "12345678900.".characterIndex(of: "0", direction: .first)
XCTAssert(res == 9)
}
func testFirstIndex2() {
let res = ".".characterIndex(of: ".", direction: .last)
XCTAssert(res == 0)
}
func testFirstIndex3() {
let res = "12345678900.".characterIndex(of: "0", direction: .last)
XCTAssert(res == 10)
}
In the Swift version 3, String doesn't have functions like -
str.index(of: String)
If the index is required for a substring, one of the ways to is to get the range. We have the following functions in the string which returns range -
str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)
For example to find the indexes of first occurrence of play in str
var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4
Note : range is an optional. If it is not able to find the String it will make it nil. For example
var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
Leo Dabus's answer is great. Here is my answer based on his answer using compactMap to avoid Index out of range error.
Swift 5.1
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]
Have you considered using NSRange?
if let range = mainString.range(of: mySubString) {
//...
}
I have an extension here of the String class in Swift that returns the index of the first letter of a given substring.
Can anybody please help me make it so it will return an array of all occurrences instead of just the first one?
Thank you.
extension String {
func indexOf(string : String) -> Int {
var index = -1
if let range = self.range(of : string) {
if !range.isEmpty {
index = distance(from : self.startIndex, to : range.lowerBound)
}
}
return index
}
}
For example instead of a return value of 50 I would like something like [50, 74, 91, 103]
You just keep advancing the search range until you can't find any more instances of the substring:
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]
I know we aren't playing code golf here, but for anyone interested in a functional style one-line implementation that doesn't use vars or loops, this is another possible solution:
extension String {
func indices(of string: String) -> [Int] {
return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
}
}
Here are 2 functions. One returns [Range<String.Index>], the other returns [Range<Int>]. If you don't need the former, you can make it private. I've designed it to mimic the range(of:options:range:locale:) method, so it supports all the same features.
import Foundation
extension String {
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<String.Index>] {
// the slice within which to search
let slice = (range == nil) ? self[...] : self[range!]
var previousEnd = s.startIndex
var ranges = [Range<String.Index>]()
while let r = slice.range(
of: aString, options: options,
range: previousEnd ..< s.endIndex,
locale: locale
) {
if previousEnd != self.endIndex { // don't increment past the end
previousEnd = self.index(after: r.lowerBound)
}
ranges.append(r)
}
return ranges
}
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<Int>] {
return allRanges(of: aString, options: options, range: range, locale: locale)
.map(indexRangeToIntRange)
}
private func indexRangeToIntRange(_ range: Range<String.Index>) -> Range<Int> {
return indexToInt(range.lowerBound) ..< indexToInt(range.upperBound)
}
private func indexToInt(_ index: String.Index) -> Int {
return self.distance(from: self.startIndex, to: index)
}
}
let s = "abc abc abc abc abc"
print(s.allRanges(of: "abc") as [Range<String.Index>])
print()
print(s.allRanges(of: "abc") as [Range<Int>])
There's not really a built-in function to do this, but we can implement a modified Knuth-Morris-Pratt algorithm to get all the indices of the string we want to match. It should also be very performant as we don't need to repeatedly call range on the string.
extension String {
func indicesOf(string: String) -> [Int] {
// Converting to an array of utf8 characters makes indicing and comparing a lot easier
let search = self.utf8.map { $0 }
let word = string.utf8.map { $0 }
var indices = [Int]()
// m - the beginning of the current match in the search string
// i - the position of the current character in the string we're trying to match
var m = 0, i = 0
while m + i < search.count {
if word[i] == search[m+i] {
if i == word.count - 1 {
indices.append(m)
m += i + 1
i = 0
} else {
i += 1
}
} else {
m += 1
i = 0
}
}
return indices
}
}
Please check the following answer for finding multiple items in multiple locations
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func attributedStringWithColor(_ strings: [String], color: UIColor, characterSpacing: UInt? = nil) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: self)
for string in strings {
let indexes = self.indicesOf(string: string)
for index in indexes {
let range = NSRange(location: index, length: string.count)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
}
}
guard let characterSpacing = characterSpacing else {return attributedString}
attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
can be used as follows :
let message = "Item 1 + Item 2 + Item 3"
message.attributedStringWithColor(["Item", "+"], color: UIColor.red)
and gets the result
This could be done with recursive method. I used a numeric string to test it. It returns an optional array of Int, meaning it will be nil if no substring can be found.
extension String {
func indexes(of string: String, offset: Int = 0) -> [Int]? {
if let range = self.range(of : string) {
if !range.isEmpty {
let index = distance(from : self.startIndex, to : range.lowerBound) + offset
var result = [index]
let substr = self.substring(from: range.upperBound)
if let substrIndexes = substr.indexes(of: string, offset: index + distance(from: range.lowerBound, to: range.upperBound)) {
result.append(contentsOf: substrIndexes)
}
return result
}
}
return nil
}
}
let numericString = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
numericString.indexes(of: "3456")
I have tweaked the accepted answer so that case sensitivity can be configured
extension String {
func allIndexes(of subString: String, caseSensitive: Bool = true) -> [Int] {
let subString = caseSensitive ? subString : subString.lowercased()
let mainString = caseSensitive ? self : self.lowercased()
var indices = [Int]()
var searchStartIndex = mainString.startIndex
while searchStartIndex < mainString.endIndex,
let range = mainString.range(of: subString, range: searchStartIndex..<mainString.endIndex),
!range.isEmpty
{
let index = distance(from: mainString.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
For a given String instance, I want to check whether the last three characters are numeric characters (0, 1, 2, ..., 9) or not.
For example, the string
let str1 = "SACH092"
should return true for such a query, whereas e.g.
let str2 = "SACHA92"
should return false for the query.
I am using Xcode 7.3.1.
(As pointed out by #NiravD, for pre Swift 3, use where to join parts of multi-clause conditions. For Swift 3, parts of multi-clause conditions are simply joined by ,. For both methods below, both Swift 2.2 and 3 versions are included)
Use pattern matching for numeric characters "0"..."9"
Swift 2.2
extension String {
var lastThreeLettersAreNumbers: Bool {
if case let chars = characters.suffix(3) where chars.count > 2 {
let numbersPattern = Character("0")..."9"
return chars.reduce(true) { $0 && (numbersPattern ~= $1) }
}
return false
}
}
Swift 3
extension String {
var lastThreeLettersAreNumbers: Bool {
if case let chars = characters.suffix(3), chars.count > 2 {
let numbersPattern = Character("0")..."9"
return chars.reduce(true) { $0 && (numbersPattern ~= $1) }
}
return false
}
}
/* example usage, common for both Swift 2.2/3 version */
let str1 = "SACH092"
let str2 = "SACH0B2"
print(str1.lastThreeLettersAreNumbers) // true
print(str2.lastThreeLettersAreNumbers) // false
Make use of nil-return Int by String initializer, with flatMap
You can make use of the fact that the Int by String initializer returns nil for strings that cannot be represented as integers.
Swift 2.2
extension String {
var lastThreeLettersAreNumbers: Bool {
if case let chars = characters.suffix(3) where chars.count > 2 {
return chars.flatMap{Int(String($0))}.count == 3
}
return false
}
}
Swift 3
extension String {
var lastThreeLettersAreNumbers: Bool {
if case let chars = characters.suffix(3), chars.count > 2 {
return chars.flatMap{Int(String($0))}.count == 3
}
return false
}
}
/* example usage, common for both Swift 2.2/3 version */
let str1 = "SACH092"
let str2 = "SACH0B2"
print(str1.lastThreeLettersAreNumbers) // true
print(str2.lastThreeLettersAreNumbers) // false
For getting last 3 characters,
let exampleString = "SACH092"
let last3Char = exampleString.substringFromIndex(exampleString.endIndex.advancedBy(-3))
Check if last3Char contains all Digits,
let badCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet
if last3Char.rangeOfCharacterFromSet(badCharacters) == nil {
print("String contains all digits")
} else {
print("String contains non-digit characters")
}
The best way to achieve string control is to use Regular Expressions.
For you :
var str = "SACH092"
let pattern = "^.*[0-9]{3,3}$"
let regexp = try! NSRegularExpression(pattern: pattern, options: [])
let matches = regexp.matches(in: str, options: [], range: NSMakeRange(0, str.characters.count))
print("End with 3 numbers : \(matches.count > 0)")
You can use like this
let s : NSString = "SACH092"
let trimmedString: String = (s as NSString).substringFromIndex(max(s.length-3,0))
print(trimmedString.isNumeric) // return true or false
//Make Extension of String
extension String {
var isNumeric: Bool {
let nums: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
return Set(self.characters).isSubsetOf(nums)
}
}
To get the last three letters of a string
let oldString = "yourString"
let newString = a.substringFromIndex(a.endIndex.advancedBy(-3))
To check those characters are numbers,
func isNumber(num: String) -> Bool {
let numberCharacters = NSCharacterSet.decimalDigitCharacterSet().invertedSet
return !num.isEmpty && num.rangeOfCharacterFromSet(numberCharacters) == nil
}
I'm used to do this in JavaScript:
var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"
Swift doesn't have this function, how to do something similar?
edit/update:
Xcode 11.4 • Swift 5.2 or later
import Foundation
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] {
ranges(of: string, options: options).map(\.lowerBound)
}
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
}
}
usage:
let str = "abcde"
if let index = str.index(of: "cd") {
let substring = str[..<index] // ab
let string = String(substring)
print(string) // "ab\n"
}
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}]
case insensitive sample
let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] } //
print(matches) // ["play", "play", "play"]
regular expression sample
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"]
Using String[Range<String.Index>] subscript you can get the sub string. You need starting index and last index to create the range and you can do it as below
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")
}
If you don't define the start index this operator ..< , it take the starting index. You can also use str[str.startIndex..<range.lowerBound] instead of str[..<range.lowerBound]
Swift 5
Find index of substring
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")
}
Find index of Character
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")
}
In Swift 4 :
Getting Index of a character in a string :
let str = "abcdefghabcd"
if let index = str.index(of: "b") {
print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}
Creating SubString (prefix and suffix) from String using Swift 4:
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)")
}
Output
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
If you want to generate a substring between 2 indices , use :
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
Doing this in Swift is possible but it takes more lines, here is a function indexOf() doing what is expected:
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"
}
This function is not optimized but it does the job for short strings.
There are three closely connected issues here:
All the substring-finding methods are over in the Cocoa NSString world (Foundation)
Foundation NSRange has a mismatch with Swift Range; the former uses start and length, the latter uses endpoints
In general, Swift characters are indexed using String.Index, not Int, but Foundation characters are indexed using Int, and there is no simple direct translation between them (because Foundation and Swift have different ideas of what constitutes a character)
Given all that, let's think about how to write:
func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
// ?
}
The substring s2 must be sought in s using a String Foundation method. The resulting range comes back to us, not as an NSRange (even though this is a Foundation method), but as a Range of String.Index (wrapped in an Optional, in case we didn't find the substring at all). However, the other number, from, is an Int. Thus we cannot form any kind of range involving them both.
But we don't have to! All we have to do is slice off the end of our original string using a method that takes a String.Index, and slice off the start of our original string using a method that takes an Int. Fortunately, such methods exist! Like this:
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
}
Or, if you prefer to be able to apply this method directly to a string, like this...
let output = "abcde".substring(from:0, toSubstring:"cd")
...then make it an extension on String:
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
}
}
Swift 5
let alphabet = "abcdefghijklmnopqrstuvwxyz"
var index: Int = 0
if let range: Range<String.Index> = alphabet.range(of: "c") {
index = alphabet.distance(from: alphabet.startIndex, to: range.lowerBound)
print("index: ", index) //index: 2
}
Swift 5
extension String {
enum SearchDirection {
case first, last
}
func characterIndex(of character: Character, direction: String.SearchDirection) -> Int? {
let fn = direction == .first ? firstIndex : lastIndex
if let stringIndex: String.Index = fn(character) {
let index: Int = distance(from: startIndex, to: stringIndex)
return index
} else {
return nil
}
}
}
tests:
func testFirstIndex() {
let res = ".".characterIndex(of: ".", direction: .first)
XCTAssert(res == 0)
}
func testFirstIndex1() {
let res = "12345678900.".characterIndex(of: "0", direction: .first)
XCTAssert(res == 9)
}
func testFirstIndex2() {
let res = ".".characterIndex(of: ".", direction: .last)
XCTAssert(res == 0)
}
func testFirstIndex3() {
let res = "12345678900.".characterIndex(of: "0", direction: .last)
XCTAssert(res == 10)
}
In the Swift version 3, String doesn't have functions like -
str.index(of: String)
If the index is required for a substring, one of the ways to is to get the range. We have the following functions in the string which returns range -
str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)
For example to find the indexes of first occurrence of play in str
var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4
Note : range is an optional. If it is not able to find the String it will make it nil. For example
var str = "play play play"
var range = str.range(of: "zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
Leo Dabus's answer is great. Here is my answer based on his answer using compactMap to avoid Index out of range error.
Swift 5.1
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]
Have you considered using NSRange?
if let range = mainString.range(of: mySubString) {
//...
}