Swift: Second Occurrence with Indexof

Swift: second occurrence with indexOf

  • List item

You can perform another search for the index of element at the remaining array slice as follow:

edit/update: Swift 5.2 or later

extension Collection where Element: Equatable {
/// Returns the second index where the specified value appears in the collection.
func secondIndex(of element: Element) -> Index? {
guard let index = firstIndex(of: element) else { return nil }
return self[self.index(after: index)...].firstIndex(of: element)
}
}


extension Collection {
/// Returns the second index in which an element of the collection satisfies the given predicate.
func secondIndex(where predicate: (Element) throws -> Bool) rethrows -> Index? {
guard let index = try firstIndex(where: predicate) else { return nil }
return try self[self.index(after: index)...].firstIndex(where: predicate)
}
}

Testing:

let numbers = [1,3,4,5,5,9,0,1]
if let index = numbers.secondIndex(of: 5) {
print(index) // "4\n"
} else {
print("not found")
}
if let index = numbers.secondIndex(where: { $0.isMultiple(of: 3) }) {
print(index) // "5\n"
} else {
print("not found")
}

Find index of Nth instance of substring in string in Swift

Xcode 11 • Swift 5 or later

let sentence = "hey hi hello, hey hi hello"
let query = "hello"
var searchRange = sentence.startIndex..<sentence.endIndex
var indices: [String.Index] = []

while let range = sentence.range(of: query, options: .caseInsensitive, range: searchRange) {
searchRange = range.upperBound..<searchRange.upperBound
indices.append(range.lowerBound)
}

print(indices) // "[7, 21]\n"

Index or range of second ocurence of bytes in file

You can find the second occurrence this way :

if let rg1 = data.range(of: mtrkChunk),
let rg2 = data[rg1.upperBound...].range(of: mtrkChunk) {
print(rg2)
}

Get the first index in a custom indexOf function in Swift 4

Your syntax with extensions is fine, so you need improvement on your algorithm logic. Break this down into two simpler problems: checking for a string within a substring, and checking if a string matches permutations of a string

What if we could compare two strings regardless of order instead of checking all permutations? If we could create a function that could return true as long as two strings had the same occurrence of letters, we wouldn't need all the permutations. Let's call this function funA(String) -> Bool

We could then call that function on a moving index within the String we're checking against (or in the case of an extension, self)

Example:

Text: Hello there

Phrase: lol

Starting index: 0, length: 3

funA(Hel) = false  
funA(ell) = false
funA(llo) = true

return 2, which is the current starting index

Finding indices for all instances of element in array

You can create your own indices method that takes a predicate as parameter:

Xcode 11 • Swift 5.1

extension Collection where Element: Equatable {
func indices(of element: Element) -> [Index] { indices.filter { self[$0] == element } }
}

extension Collection {
func indices(where isIncluded: (Element) throws -> Bool) rethrows -> [Index] { try indices.filter { try isIncluded(self[$0]) } }
}

let arr = [1, 2, 3, 1, 0, 1, 2, 2, 3, 1, 1, 2]
let search = 1

let indices = arr.indices(where: { $0 == search })
// or simply
// let indices = arr.indices { $0 == search }
print(indices) // [0, 3, 5, 9, 10]

let indices2 = arr.indices(of: search)
print(indices2) // [0, 3, 5, 9, 10]

let string = "Hello World !!!"
let indices3 = string.indices(of: "o")
print(indices3) // [Swift.String.Index(_compoundOffset: 16, _cache: Swift.String.Index._Cache.character(1)), Swift.String.Index(_compoundOffset: 28, _cache: Swift.String.Index._Cache.character(1))]

How to get index(of:) to return multiple indices?

You might use a combination of enumerated and compactMap:

let indexArray = group.enumerated().compactMap {
$0.element == "A" ? $0.offset : nil
}
print(indexArray) // [10, 12, 13, 27]

Swift find all occurrences of a substring

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]

Finding index of character in Swift String

You are not the only one who couldn't find the solution.

String doesn't implement RandomAccessIndexType. Probably because they enable characters with different byte lengths. That's why we have to use string.characters.count (count or countElements in Swift 1.x) to get the number of characters. That also applies to positions. The _position is probably an index into the raw array of bytes and they don't want to expose that. The String.Index is meant to protect us from accessing bytes in the middle of characters.

That means that any index you get must be created from String.startIndex or String.endIndex (String.Index implements BidirectionalIndexType). Any other indices can be created using successor or predecessor methods.

Now to help us with indices, there is a set of methods (functions in Swift 1.x):

Swift 4.x

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 3.0

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

Swift 1.x

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
let index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times

Working with String.Index is cumbersome but using a wrapper to index by integers (see https://stackoverflow.com/a/25152652/669586) is dangerous because it hides the inefficiency of real indexing.

Note that Swift indexing implementation has the problem that indices/ranges created for one string cannot be reliably used for a different string, for example:

Swift 2.x

let text: String = "abc"
let text2: String = "br>
let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

Swift 1.x

let text: String = "abc"
let text2: String = "br>
let range = text.rangeOfString("b")

//can randomly return nil or a bad substring
let substring: String = text2[range]

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]


Related Topics



Leave a reply



Submit