Implementing a custom asynchronous sequence in Swift - swift

Imagine I want to create a function that, given an array of numbers, computes the square, cube, and fourth power of each number in an asynchronous fashion and returns a flattened, asynchronous sequence of all these results.
So, for example, for the input array [2, 3, 4], it should return an AsyncSequence instance yielding the elements [4, 8, 16, 9, 27, 81, 16, 64, 256].
Then let's say, instead of computing x^2, x^3, x^4, I would like it to compute x, x^2, x^3, ..., x^k where k is sort of a random integer that can be different for every x and is not known beforehand (its value comes to be known only as the powers are being computed). How would I implement such a pattern?

An AsyncStream could do the job. E.g., given an array of integers, values, the asynchronous sequence would be:
let stream = AsyncStream<Int> { continuation in
Task.detached {
for value in values {
var result = value
for _ in 1 ..< n {
result *= value
continuation.yield(result)
}
}
continuation.finish()
}
}
But this calculation of x², x³, ..., xⁿ for each element in the input array might not be a good candidate for an asynchronous sequence. Each subsequent value can be calculated nearly instantaneously (just multiplying the previously emitted value by x) and, as such, should probably just be a standard, synchronous sequence.
Generally, asynchronous sequences should be those that are sufficiently slow to justify moving it into the background or otherwise has results that are emitted asynchronously over time.

Thanks a lot to Rob for providing the basic idea on how to implement something like this.
I wrote it in the following way:
func powers(of numbers: [Int]) -> AsyncStream<Int> {
return AsyncStream<Int> { continuation in
Task {
for number in numbers {
for await power in Powers(of: number) {
continuation.yield(power)
}
}
continuation.finish()
}
}
}
struct Powers: AsyncSequence {
init(of base: Int) {
self.base = base
}
func makeAsyncIterator() -> PowersIterator {
return PowersIterator(base: self.base)
}
let base: Int
typealias Element = Int
}
struct PowersIterator: AsyncIteratorProtocol {
mutating func next() async -> Int? {
if !self.shouldFinish() {
try? await Task.sleep(nanoseconds: 1_000_000_000)
defer {
self.exponent += 1
}
return power(self.base, self.exponent)
} else {
return nil
}
}
private func shouldFinish() -> Bool {
return Int.random(in: 1...10) == 1
}
private func power(_ base: Int, _ exponent: UInt) -> Int {
return (0..<exponent).reduce(1) { power, _ in power * base }
}
var exponent = UInt(1)
let base: Int
}
It can be invoked using this code:
Task {
let numbers = [1, 2, 3, 4, 5]
for await power in powers(of: numbers) {
print(power, terminator: " ")
}
}
Possible output:
1 1 1 2 4 8 16 32 64 3 9 27 4 16 64 256 5 25 125 625 3125 15625
The solution is a little more complex than it ought to be. But that's of course because I actually wanted to compute something that would really need to be computed asynchronously and has the same computational structure as this example. That is also the reason for why I created a separate async sequence for computing the powers.
If this helps anyone out, I'll be glad.

Related

I need to compare 3 elements in a row in an array, but i get index out of range error

The problem: to return a number of zeros in an array that contains 0s and 1s, if there are 3 0s in a row, count them as one, for example [0, 1, 0, 0, 0, 1, 0, 1, 0] should return 4, but when i try to solve it like this
func findZeros(_ c: [Int]) -> Int {
var zeros = 0
for var i in 0..<c.count {
switch c[i] {
case _ where c[i] == 0 && c[i+1] == 0 && c[i+2] == 0: // row 5
zeros += 1
i += 2
case _ where c[i] == 0:
zeros += 1
default:
break
}
}
return zeros
}
i always get index out of range error in row 5 , although when i hardcode c[1], c[2], c[3] == 0, it just counts as false and goes through... i've just started to learn swift so maybe that's not optimal, but anyway i can't even get this one working :/
You can group the consecutive elements and sum how many groups you have of those elements:
extension Collection where Element: Equatable {
var grouped: [[Element]] {
reduce(into: []) {
// check if the last element of the last collection is equal to the current element
$0.last?.last == $1 ?
// append the element to the last collection
$0[$0.index(before: $0.endIndex)].append($1) :
// otherwise add a new collection with the new element
$0.append([$1])
}
}
func repeatedOccurences(of element: Element) -> Int {
// if the collection first element is equal to the element add one otherwise return the current result
grouped.reduce(0) { $1.first == element ? $0 + 1 : $0 }
}
}
[0, 1, 0, 0, 0, 1, 0, 1, 0].repeatedOccurences(of: 0) // 4
[0, 1, 0, 0, 0, 1, 0, 1, 0].repeatedOccurences(of: 1) // 3
If you were only accessing c[i], then counting i up to c.count - 1 would be fine - but you're also trying to access c[i+1] and c[i+2], so you need to take that into account when setting the upper boundary of the range - and then verify that the array indeed has at least 3 elements:
if c.count < 3 {
return 0;
}
for var i in 0..<c.count-2 {
// now you can safely access c[i+2]
}
I'm going to show a very different solution approach. It's not one that would come naturally to a new beginner, so don't worry if it's foreign to you. It's something that you might come up with from having a bit more experience and being able to relate scattered concepts with each other.
The over-all process is actually very straight-forward, and is an almost exact codification of the your english explanation to the solution:
Identify all the sub-sequences of repeating elements. These are called "runs", and there's a concept called a "run-length encoding". It takes an input like ["A", "B, B", "C", "D", "D", "D"], and turns it into a sequence like [("A", 1), ("B", 2), ("C", 1), ("D", 3)]
Identify the runs of 0s that repeat precisely 3 times, and treat them as runs of a single 0.
Count the number of 0s in the runs.
Here's what the code to do that would look like:
let input = [0, 1, 0, 0, 0, 1, 0, 1, 0]
let result = input.runLengthEncoded()
.lazy
.filter(keepOnlyRunsOfZeros)
.map(convertThreeCountRunsIntoOneCountRuns)
.map { $0.count }
.reduce(0, +)
print(result)
And here are the supporting functions that make it possible:
typealias Run = (element: Int, count: Int)
func keepOnlyRunsOfZeros(_ run: Run) -> Bool {
return run.element == 0
}
func convertThreeCountRunsIntoOneCountRuns(_ run: Run) -> Run {
if run.count == 3 {
return (element: run.element, count: 1)
}
else {
return run
}
}
Knowing that the process in #1 is a known algorithm called run-length encoding, I can find and reuse an existing implementation. Over time as a developer, you build up a collection of useful functions/techniques that you reuse for future use. Often times, you find other people's libraries that you've come to find useful, thus have booked-marked and re-use.
In this case, I have an implementation of run-length encoding that I've written and used in previous projects. It's quite long, but it's generalized and lazy-evaluated (it doesn't need to make an array of all runs, it serves them one by one as you request them, which improves performance for huge inputs), which isn't strictly necessary in this case, but it's what I already have on hand.
There alternative implementations that you can find that are eagerly evaluated and less generic (which should be fine for a simple problem like this), feel free to substitute one of those, instead.
public extension Sequence where Self.Iterator.Element: Equatable {
func runLengthEncoded() -> LazySequenceRunLengthEncoder<Self> {
return LazySequenceRunLengthEncoder(encoding: self)
}
}
public struct LazySequenceRunLengthEncoder<WrappedSequence: Sequence>: Sequence
where WrappedSequence.Element: Equatable {
public let wrappedSequence: WrappedSequence
public init(encoding wrappedSequence: WrappedSequence) {
self.wrappedSequence = wrappedSequence
}
public func makeIterator() -> RunLengthEncodingIterator<WrappedSequence.Iterator> {
return RunLengthEncodingIterator(encoding: wrappedSequence.makeIterator())
}
}
public struct RunLengthEncodingIterator<WrappedIterator: IteratorProtocol>: IteratorProtocol
where WrappedIterator.Element: Equatable {
public private(set) var wrappedIterator: WrappedIterator
public private(set) var currentGrouping: (element: WrappedIterator.Element, count: Int)? = nil
public init(encoding wrappedIterator: WrappedIterator) {
self.wrappedIterator = wrappedIterator
}
public mutating func next() -> (element: WrappedIterator.Element, count: Int)? {
while let newElement = wrappedIterator.next() { // Take all elements of this run
if let currentGrouping = self.currentGrouping {
if newElement == currentGrouping.element { // increment the current run
let newCount = currentGrouping.count + 1
self.currentGrouping = (element: newElement, count: newCount)
} else { // Broke the streak
defer {
self.currentGrouping = (element: newElement, count: 1) // start a new group
}
return self.currentGrouping
}
} else { // There is no current group, this is the first element
self.currentGrouping = (element: newElement, count: 1)
}
}
// Reached end of the wrapped iterator
// 2. Only return the current grouping once, return the `nil` next time to end this iterator.
defer { self.currentGrouping = nil }
// 1. Return current grouping, if there is one
return self.currentGrouping
}
}

How to compare characters in Swift efficiently

I have a function in Swift that computes the hamming distance of two strings and then puts them into a connected graph if the result is 1.
For example, read to hear returns a hamming distance of 2 because read[0] != hear[0] and read[3] != hear[3].
At first, I thought my function was taking a long time because of the quantity of input (8,000+ word dictionary), but I knew that several minutes was too long. So, I rewrote my same algorithm in Java, and the computation took merely 0.3s.
I have tried writing this in Swift two different ways:
Way 1 - Substrings
extension String {
subscript (i: Int) -> String {
return self[Range(i ..< i + 1)]
}
}
private func getHammingDistance(w1: String, w2: String) -> Int {
if w1.length != w2.length { return -1 }
var counter = 0
for i in 0 ..< w1.length {
if w1[i] != w2[i] { counter += 1 }
}
return counter
}
Results: 434 seconds
Way 2 - Removing Characters
private func getHammingDistance(w1: String, w2: String) -> Int {
if w1.length != w2.length { return -1 }
var counter = 0
var c1 = w1, c2 = w2 // need to mutate
let length = w1.length
for i in 0 ..< length {
if c1.removeFirst() != c2.removeFirst() { counter += 1 }
}
return counter
}
Results: 156 seconds
Same Thing in Java
Results: 0.3 seconds
Where it's being called
var graph: Graph
func connectData() {
let verticies = graph.canvas // canvas is Array<Node>
// Node has key that holds the String
for vertex in 0 ..< verticies.count {
for compare in vertex + 1 ..< verticies.count {
if getHammingDistance(w1: verticies[vertex].key!, w2: verticies[compare].key!) == 1 {
graph.addEdge(source: verticies[vertex], neighbor: verticies[compare])
}
}
}
}
156 seconds is still far too inefficient for me. What is the absolute most efficient way of comparing characters in Swift? Is there a possible workaround for computing hamming distance that involves not comparing characters?
Edit
Edit 1: I am taking an entire dictionary of 4 and 5 letter words and creating a connected graph where the edges indicate a hamming distance of 1. Therefore, I am comparing 8,000+ words to each other to generate edges.
Edit 2: Added method call.
Unless you chose a fixed length character model for your strings, methods and properties such as .count and .characters will have a complexity of O(n) or at best O(n/2) (where n is the string length). If you were to store your data in an array of character (e.g. [Character] ), your functions would perform much better.
You can also combine the whole calculation in a single pass using the zip() function
let hammingDistance = zip(word1.characters,word2.characters)
.filter{$0 != $1}.count
but that still requires going through all characters of every word pair.
...
Given that you're only looking for Hamming distances of 1, there is a faster way to get to all the unique pairs of words:
The strategy is to group words by the 4 (or 5) patterns that correspond to one "missing" letter. Each of these pattern groups defines a smaller scope for word pairs because words in different groups would be at a distance other than 1.
Each word will belong to as many groups as its character count.
For example :
"hear" will be part of the pattern groups:
"*ear", "h*ar", "he*r" and "hea*".
Any other word that would correspond to one of these 4 pattern groups would be at a Hamming distance of 1 from "hear".
Here is how this can be implemented:
// Test data 8500 words of 4-5 characters ...
var seenWords = Set<String>()
var allWords = try! String(contentsOfFile: "/usr/share/dict/words")
.lowercased()
.components(separatedBy:"\n")
.filter{$0.characters.count == 4 || $0.characters.count == 5}
.filter{seenWords.insert($0).inserted}
.enumerated().filter{$0.0 < 8500}.map{$1}
// Compute patterns for a Hamming distance of 1
// Replace each letter position with "*" to create patterns of
// one "non-matching" letter
public func wordH1Patterns(_ aWord:String) -> [String]
{
var result : [String] = []
let fullWord : [Character] = aWord.characters.map{$0}
for index in 0..<fullWord.count
{
var pattern = fullWord
pattern[index] = "*"
result.append(String(pattern))
}
return result
}
// Group words around matching patterns
// and add unique pairs from each group
func addHamming1Edges()
{
// Prepare pattern groups ...
//
var patternIndex:[String:Int] = [:]
var hamming1Groups:[[String]] = []
for word in allWords
{
for pattern in wordH1Patterns(word)
{
if let index = patternIndex[pattern]
{
hamming1Groups[index].append(word)
}
else
{
let index = hamming1Groups.count
patternIndex[pattern] = index
hamming1Groups.append([word])
}
}
}
// add edge nodes ...
//
for h1Group in hamming1Groups
{
for (index,sourceWord) in h1Group.dropLast(1).enumerated()
{
for targetIndex in index+1..<h1Group.count
{ addEdge(source:sourceWord, neighbour:h1Group[targetIndex]) }
}
}
}
On my 2012 MacBook Pro, the 8500 words go through 22817 (unique) edge pairs in 0.12 sec.
[EDIT] to illustrate my first point, I made a "brute force" algorithm using arrays of characters instead of Strings :
let wordArrays = allWords.map{Array($0.unicodeScalars)}
for i in 0..<wordArrays.count-1
{
let word1 = wordArrays[i]
for j in i+1..<wordArrays.count
{
let word2 = wordArrays[j]
if word1.count != word2.count { continue }
var distance = 0
for c in 0..<word1.count
{
if word1[c] == word2[c] { continue }
distance += 1
if distance > 1 { break }
}
if distance == 1
{ addEdge(source:allWords[i], neighbour:allWords[j]) }
}
}
This goes through the unique pairs in 0.27 sec. The reason for the speed difference is the internal model of Swift Strings which is not actually an array of equal length elements (characters) but rather a chain of varying length encoded characters (similar to the UTF model where special bytes indicate that the following 2 or 3 bytes are part of a single character. There is no simple Base+Displacement indexing of such a structure which must always be iterated from the beginning to get to the Nth element.
Note that I used unicodeScalars instead of Character because they are 16 bit fixed length representations of characters that allow a direct binary comparison. The Character type isn't as straightforward and take longer to compare.
Try this:
extension String {
func hammingDistance(to other: String) -> Int? {
guard self.characters.count == other.characters.count else { return nil }
return zip(self.characters, other.characters).reduce(0) { distance, chars in
distance + (chars.0 == chars.1 ? 0 : 1)
}
}
}
print("read".hammingDistance(to: "hear")) // => 2
The following code executed in 0.07 secounds for 8500 characters:
func getHammingDistance(w1: String, w2: String) -> Int {
if w1.characters.count != w2.characters.count {
return -1
}
let arr1 = Array(w1.characters)
let arr2 = Array(w2.characters)
var counter = 0
for i in 0 ..< arr1.count {
if arr1[i] != arr2[i] { counter += 1 }
}
return counter
}
After some messing around, I found a faster solution to #Alexander's answer (and my previous broken answer)
extension String {
func hammingDistance(to other: String) -> Int? {
guard !self.isEmpty, !other.isEmpty, self.characters.count == other.characters.count else {
return nil
}
var w1Iterator = self.characters.makeIterator()
var w2Iterator = other.characters.makeIterator()
var distance = 0;
while let w1Char = w1Iterator.next(), let w2Char = w2Iterator.next() {
distance += (w1Char != w2Char) ? 1 : 0
}
return distance
}
}
For comparing strings with a million characters, on my machine it's 1.078 sec compared to 1.220 sec, so roughly a 10% improvement. My guess is this is due to avoiding .zip and the slight overhead of .reduce and tuples
As others have noted, calling .characters repeatedly takes time. If you convert all of the strings once, it should help.
func connectData() {
let verticies = graph.canvas // canvas is Array<Node>
// Node has key that holds the String
// Convert all of the keys to utf16, and keep them
let nodesAsUTF = verticies.map { $0.key!.utf16 }
for vertex in 0 ..< verticies.count {
for compare in vertex + 1 ..< verticies.count {
if getHammingDistance(w1: nodesAsUTF[vertex], w2: nodesAsUTF[compare]) == 1 {
graph.addEdge(source: verticies[vertex], neighbor: verticies[compare])
}
}
}
}
// Calculate the hamming distance of two UTF16 views
func getHammingDistance(w1: String.UTF16View, w2: String.UTF16View) -> Int {
if w1.count != w2.count {
return -1
}
var counter = 0
for i in w1.startIndex ..< w1.endIndex {
if w1[i] != w1[i] {
counter += 1
}
}
return counter
}
I used UTF16, but you might want to try UTF8 depending on the data. Since I don't have the dictionary you are using, please let me know the result!
*broken*, see new answer
My approach:
private func getHammingDistance(w1: String, w2: String) -> Int {
guard w1.characters.count == w2.characters.count else {
return -1
}
let countArray: Int = w1.characters.indices
.reduce(0, {$0 + (w1[$1] == w2[$1] ? 0 : 1)})
return countArray
}
comparing 2 strings of 10,000 random characters took 0.31 seconds
To expand a bit: it should only require one iteration through the strings, adding as it goes.
Also it's way more concise 🙂.

How to split or iterate over an Int without converting to String in Swift [duplicate]

This question already has answers here:
Break A Number Up To An Array of Individual Digits
(6 answers)
Closed 5 years ago.
I was wondering if there was a way in Swift to split an Int up into it's individual digits without converting it to a String. For example:
let x: Int = 12345
//Some way to loop/iterate over x's digits
//Then map each digit in x to it's String value
//Return "12345"
For a bit of background, I'm attempting to create my own method of converting an Int to a String without using the String description property or using String Interpolation.
I've found various articles on this site but all the ones I've been able to find either start with a String or end up using the String description property to convert the Int to a String.
Thanks.
Just keep dividing by 10 and take the remainder:
extension Int {
func digits() -> [Int] {
var digits: [Int] = []
var num = self
repeat {
digits.append(num % 10)
num /= 10
} while num != 0
return digits.reversed()
}
}
x.digits() // [1,2,3,4,5]
Note that this will return all negative digits if the value is negative. You could add a special case if you want to handle that differently. This return [0] for 0, which is probably what you want.
And because everyone like pure functional programming, you can do it that way too:
func digits() -> [Int] {
let partials = sequence(first: self) {
let p = $0 / 10
guard p != 0 else { return nil }
return p
}
return partials.reversed().map { $0 % 10 }
}
(But I'd probably just use the loop here. I find sequence too tricky to reason about in most cases.)
A recursive way...
extension Int {
func createDigitArray() -> [Int] {
if self < 10 {
return [self]
} else {
return (self / 10).createDigitArray() + [self % 10]
}
}
}
12345.createDigitArray() //->[1, 2, 3, 4, 5]
A very easy approach would be using this function:
func getDigits(of number: Int) -> [Int] {
var digits = [Int]()
var x = number
repeat{
digits.insert(abs(x % 10), at: 0)
x/=10
} while x != 0
return digits
}
And using it like this:
getDigits(of: 97531) // [9,7,5,3,1]
getDigits(of: -97531) // [9,7,5,3,1]
As you can see, for a negative number you will receive the array of its digits, but at their absolute value (e.g.: -9 => 9 and -99982 => 99982)
Hope it helps!

Converting a C-style for loop that uses division for the step to Swift 3

I have this loop, decrementing an integer by division, in Swift 2.
for var i = 128; i >= 1 ; i = i/2 {
//do some thing
}
The C-style for loop is deprecated, so how can I convert this to Swift 3.0?
Quite general loops with a non-constant stride can be realized
with sequence:
for i in sequence(first: 128, next: { $0 >= 2 ? $0/2 : nil }) {
print(i)
}
Advantages: The loop variable i is a constant and its scope is
restricted to the loop body.
Possible disadvantages: The terminating condition must be adapted
(here: $0 >= 2 instead of i >= 1), and the loop is always executed
at least once, for the first value.
One could also write a wrapper which resembles the C-style for loop
more closely and does not have the listed disadvantages
(inspired by Erica Sadun: Stateful loops and sequences):
public func sequence<T>(first: T, while condition: #escaping (T)-> Bool, next: #escaping (T) -> T) -> UnfoldSequence<T, T> {
let nextState = { (state: inout T) -> T? in
guard condition(state) else { return nil }
defer { state = next(state) }
return state
}
return sequence(state: first, next: nextState)
}
and then use it as
for i in sequence(first: 128, while: { $0 >= 1 }, next: { $0 / 2 }) {
print(i)
}
MartinR's solution is very generic and useful and should be part of your toolbox.
Another approach is to rephrase what you want: the powers of two from 7 down to 0.
for i in (0...7).reversed().map({ 1 << $0 }) {
print(i)
}
I'll suggest that you should use a while loop to handle this scenario:
var i = 128
while i >= 1
{
// Do your stuff
i = i / 2
}

Swift: second occurrence with indexOf

let numbers = [1,3,4,5,5,9,0,1]
To find the first 5, use:
numbers.indexOf(5)
How do I find the second occurence?
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")
}
Once you've found the first occurrence, you can use indexOf on the remaining slice of the array to locate the second occurrence:
let numbers = [1,3,4,5,5,9,0,1]
if let firstFive = numbers.indexOf(5) { // 3
let secondFive = numbers[firstFive+1..<numbers.count].indexOf(5) // 4
}
I don't think you can do it with indexOf. Instead you'll have to use a for-loop. A shorthand version:
let numbers = [1,3,4,5,5,9,0,1]
var indexes = [Int]()
numbers.enumerate().forEach { if $0.element == 5 { indexes += [$0.index] } }
print(indexes) // [3, 4]
Here's a general use extension of Array that will work for finding the nth element of a kind in any array:
extension Array where Element: Equatable {
// returns nil if there is no nth occurence
// or the index of the nth occurence if there is
func findNthIndexOf(n: Int, thing: Element) -> Int? {
guard n > 0 else { return nil }
var count = 0
for (index, item) in enumerate() where item == thing {
count += 1
if count == n {
return index
}
}
return nil
}
}
let numbers = [1,3,4,5,5,9,0]
numbers.findNthIndexOf(2, thing: 5) // returns 4
EDIT: as per #davecom's comment, I've included a similar but slightly more complex solution at the bottom of the answer.
I see a couple of good solutions here, especially considering the limitations the relatively new language of Swift. There is a really concise way to do it too, but beware...it is rather quick-and-dirty. May not be the perfect solution, but it is pretty quick. Also very versatile (not to brag).
extension Array where Element: Equatable {
func indexes(search: Element) -> [Int] {
return enumerate().reduce([Int]()) { $1.1 == search ? $0 + [$1.0] : $0 }
}
}
Using this extension, you could access the second index as follows:
let numbers = [1, 3, 4, 5, 5, 9, 0, 1]
let indexesOf5 = numbers.indexes(5) // [3, 4]
indexesOf5[1] // 4
And you're done!
Basically, the method works like this: enumerate() maps the array to tuples including the index of each element with the element itself. In this case, [1, 3, 4, 5, 5, 9, 0, 1].enumerate() returns a collection of the type EnumerateSequence<Array<Int>> which, translated to an Integer array, returns [(0,1), (1,3), (2,4), (3,5), (4,5), (5,9), (6,0), (7,1)].
The rest of the work is done using reduce (called 'inject' in some languages), which is an extremely powerful tool that many coders are not familiar with. If the reader is among those coders, I'd recommend checking out this article regarding use of the function in JS (keep in mind the placement of the non-block argument passed in is inputted after the block in JS, rather than before as seen here).
Thanks for reading.
P.S. not to be too long-winded on this relatively simple solution, but if the syntax for the indexes method shown above is a bit too quick-and-dirty, you could try something like this in the method body, where the closure's parameters are expanded for a bit more clarity:
return enumerate().reduce([Int]()) { memo, element in
element.1 == search ? memo + [element.0] : memo
}
EDIT: Here's another option that allows the implementer to scan for a specific "index at index" (e.g. the second occurrence of 5) for a more efficient solution.
extension Array where Element: Equatable {
func nIndex(search: Element, n: Int) -> Int? {
let info = enumerate().reduce((count: 0, index: 0), combine: { memo, element in
memo.count < n && element.1 == search ? (count: memo.count + 1, index: element.0) : memo
})
return info.count == n ? info.index : nil
}
}
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 2) // 4
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 3) // nil
The new method still iterates over the entire array, but is much more efficient due to the lack of "array-building" in the previous method. That performance hit would be negligible with the 8-object array used for the majority. But consider a list of 10,000 random numbers from 0 to 99:
let randomNumbers = (1...10000).map{_ in Int(rand() % 100)}
let indexes = randomNumbers.indexes(93) // count -> 100 (in my first run)
let index1 = indexes[1] // 238
// executed in 29.6603130102158 sec
let index2 = randomNumbers.nIndex(93, n: 2) // 238
// executed in 3.82625496387482 sec
As can be seen, this new method is considerably faster with the (very) large dataset; it is a bit more cumbersome and confusing though, so depending on your application, you may prefer the simpler solution, or a different one entirely.
(Again) thanks for reading.
extension Collection where Element: Equatable {
func nth(occurance: Int, of element: Element) -> Index? {
var level : Int = occurance
var position = self.startIndex
while let index = self[position...].index(of: element) {
level -= 1
guard level >= 0 else { return nil }
guard level != 0 else { return index }
position = self.index(after: index)
}
return nil
}
}