Reverse Zip a Collection - swift

I'm looking to do a "reverse zip" on a collection. Essentially meaning:
let a = [1, 2, 3, 4, 5, 6, 7]
let (left, right) = a.reverseZip()
print(left) // [1, 3, 5, 7]
print(right) // [2, 4, 6]
I have an implementation that seems to work (see my answer listed below), but I have two questions that I'm hoping we could discuss:
What's the correct name for this kind of operation? It's not exactly the opposite of zip (and I'm certain this pattern exists elsewhere in another functional language)
Is there a more optimal algorithm for this method?

This is a seed of an idea. Instead of returning arrays, it would be more efficient to return sequences.
Here is an Array extension that returns two LazyMapSequence<StrideTo<Int>, Element>. The advantage is that is doesn't actually generate the sequences; they are provided on demand because they're lazy. This is much like the way reversed() works on an Array.
extension Array {
func reverseZip() -> (LazyMapSequence<StrideTo<Int>, Element>, LazyMapSequence<StrideTo<Int>, Element>) {
let left = stride(from: 0, to: self.count, by: 2).lazy.map { self[$0] }
let right = stride(from: 1, to: self.count, by: 2).lazy.map { self[$0] }
return (left, right)
}
}
Example:
let a = [1, 2, 3, 4, 5, 6, 7]
let (left, right) = a.reverseZip()
// iterate left
for i in left {
print(i)
}
1
3
5
7
// turn them into arrays to print them
print(Array(left))
[1, 3, 5, 7]
print(Array(right))
[2, 4, 6]
I'm sure this can be generalized to something more general than Array. That is left as an exercise to the reader.
If you want two arrays
If you really want the two arrays, then you'd just return the result of map (taking out lazy):
extension Array {
func reverseZip() -> ([Element], [Element]) {
let left = stride(from: 0, to: self.count, by: 2).map { self[$0] }
let right = stride(from: 1, to: self.count, by: 2).map { self[$0] }
return (left, right)
}
}

Based on vacawama's idea to return lazy collections instead of arrays,
here is a possible generalization to arbitrary collections with arbitrary strides:
extension Collection {
func makeStrideIterator(stride: Int) -> AnyIterator<Element> {
precondition(stride > 0, "The stride must be positive")
var it = makeIterator()
var first = true
return AnyIterator<Element> {
if first {
first = false
} else {
// Skip `stride - 1` elements:
for _ in 1..<stride {
guard let _ = it.next() else { return nil }
}
}
return it.next()
}
}
}
The method returns an AnyIterator (which is both an iterator and a sequence)
of the collection elements at the positions
0, stride, 2*stride, ...
Unzipping an array (or any other collection) can then be done with
let a = [1, 2, 3, 4, 5]
let left = a.makeStrideIterator(stride: 2)
let right = a.dropFirst().makeStrideIterator(stride: 2)
for e in left { print(e) }
// 1, 3, 5
for e in right { print(e) }
// 2, 4
Here is an example for picking every third character from a string:
for c in "ABCDEFG".makeStrideIterator(stride: 3) {
print(c)
}
// A D G
The special case of unzipping into two arrays can then be implemented as
extension Collection {
func unzip() -> ([Element], [Element]) {
return (Array(makeStrideIterator(stride: 2)),
Array(dropFirst().makeStrideIterator(stride: 2)))
}
}
Example:
print([1, 2, 3, 4, 5].unzip())
// ([1, 3, 5], [2, 4])

extension Collection {
func reverseZip() -> ([Element], [Element]) {
var left = [Element]()
var right = [Element]()
let capacity = Double(self.count) / 2.0
left .reserveCapacity(Int(ceil(capacity)))
right.reserveCapacity(self.count / 2)
for element in self {
if left.count > right.count {
right.append(element)
} else {
left.append(element)
}
}
return (left, right)
}
}
UPDATE (03/10/18)
Thank you all for your help. Seems like a lot of you latched onto Stride, which is definitely a good way to go. I resisted this approach for 2 reasons:
I'm keen to keep this function as an extension of Collection, rather than Array
Strictly speaking, the startIndex of a given Collection isn't necessary 0, and the progression of indexes isn't necessarily an increment of 1 each time.
Martin R's approach of using a custom iterator definitely seemed like the right approach, so I used that along with the WWDC 2018's implementation of "EveryOther" to create a standard sequential iterator that simply goes along two elements in each while loop block, till it hits the end.
extension Collection {
func deinterleave() -> ([Element], [Element]) {
let smallHalf = count / 2
let bigHalf = count - smallHalf
var left = [Element]()
var right = [Element]()
left .reserveCapacity(bigHalf)
right.reserveCapacity(smallHalf)
let start = self.startIndex
let end = self.endIndex
var iter = start
while iter != end {
left.append(self[iter])
iter = index(after: iter)
if iter == end { break }
right.append(self[iter])
iter = index(after: iter)
}
return (left, right)
}
}
let a = [1,2,3,4,5,6,7]
print(a.deinterleave()) // ([1, 3, 5, 7], [2, 4, 6])
Without a doubt, there is some scope to improve this further and use vacawama's LazyMapSequence implementation, but for now this does everything I need it to.

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
}
}

TwoSum Swift Solution

I just started learning coding with swift, and was trying TwoSum.
"Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1]."
I found some solutions from GitHub that I cannot understand.
code is from https://github.com/soapyigu/LeetCode-Swift/blob/master/Array/TwoSum.swift
class TwoSum {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dict = [Int: Int]()
for (i, num) in nums.enumerated() {
if let lastIndex = dict[target - num] {
return [lastIndex, i]
}
dict[num] = i
}
fatalError("No valid outputs")
}
}
Could someone be so kind to explain to codes. Thanks a lot.
The dict initialised in the method stores the numbers in the input as keys, and their indices as values. The program uses this to remember which number is where. The dict can tell you things like "the number 2 is at index 0".
For each number num at index i in the input array, we subtract num from the target to find the other number that we need, in order for them to add up to target.
Now we have the other number we need, we check to see if we have seen such a number before, by searching dict. This is what the if let lastIndex = dict[target - num] part is doing. If the dict knows what index the other number is at, we return that index, and i.
If we haven't seen that number before, we record i into the dictionary under the key num, hoping that in later iterations, we can find a number that when added to num, makes 9.
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
var arr:[Int] = []
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var toggle = false
for i in 0..<nums.count {
for j in i+1..<nums.count {
if toggle == false {
if(nums[i]+nums[j]==target){
toggle = true
arr.insert(i, at: 0)
arr.insert(j, at: 1)
break
}
}
}
}
return arr
}
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
In Sweeper's excellent answer, he explained what dict is used for: It lets you use a value from the array to find that value's index. It would be more obvious what the dictionary was used for if we called it indexes, and this code builds the same dictionary in a more explicit way:
var indexes = [Int: Int]()
for index in 0..<array.count {
let value = array[index]
indexes[value] = index
}
After that, you get a dictionary:
[2:0, 7:1, 11:2, 15:3]
You could write the function this way:
func twoSum(_ array: [Int], _ target: Int) -> [Int] {
var indexes = [Int: Int]()
for index in 0..<array.count {
let value = array[index]
indexes[value] = index
}
for index in 0..<array.count {
let value = array[index]
if let otherIndex = indexes[target - value],
index != otherIndex {
return [index, otherIndex]
}
}
fatalError("Unable to match values")
}
That is a much more long-winded (and less efficient) way of doing the same thing. It loops through the array twice instead of once, but the results should be the same.
func twoSum(array: [Int], target: Int) -> [Int] {
var dict = [Int:Int]()
for (index, number) in array.enumerated() {
let value = target - number
if let sum = dict[value] {
return [sum, index]
}
dict[number] = index
}
return [0,0]
}
/*
array=[1, 2, 3] -> target=4
enumerated() => [0,1], [1,2], [2,3]
(i, n)
v4 - 1 = 3
s[3:0]
s[3:0]
v4 - 2 = 2
s[2:0]
s[2:1]
v4 - 3 = 1
s[1:1]
s[1:2]
output [0,2]
*/
var numbers: [Int] = [1, 3, 6, 7, 7, 14, 12]
var target = 26
var result = [Int]()
for i in 0..<numbers.count {
for j in i+1..<numbers.count {
if numbers[i] + numbers[j] == target {
print(numbers[i],numbers[j])
result.append(i)
result.append(j)
}
}
}
print(Array(Set(result)))
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dict:[Int:Int] = [:]
for i in 0..<nums.count {
if dict[target - nums[i]] != nil {
return [dict[target - nums[i]] ?? 0, i]
} else {
dict[nums[i]] = i
}
}
return [0]
}
Here is a link to the discussion section of the TwoSum problem on Leetcode.
Lots of great Swift solutions there.
https://leetcode.com/problems/two-sum/discuss/?currentPage=1&orderBy=most_votes&query=swift.
My personal two cents -
func twoSumA(_ nums: [Int], _ target: Int) -> [Int] {
var numsHashMap: Dictionary<Int, Int> = [:]
var outputArr: [Int] = []
for index in 0..<nums.count {
let currentNum = nums[index]
if numsHashMap.keys.contains(target-currentNum) {
outputArr.append(numsHashMap[target-currentNum] ?? -1)
outputArr.append(index)
return outputArr
}
numsHashMap[currentNum] = index
}
return !outputArr.isEmpty ? outputArr : [-1, -1]
}

Swift Subscript Function - Allowing Multiple Return Types

I am creating a Matrix library for swift that can support variable-dimensional matrices (not just your standard 2D matrix).
It is going quite well, however I am running into one problem
I would like the subscript() function to either return a value or an array of values.
So if you have a simple 2D matrix like this:
let a : Matrix<Int> = [[1, 2, 3], [4, 5, 6]] // constructs a Matrix
And the user subscripts into this matrix like this:
b = a[1, 0] //returns 4
However if the user subscripts like this:
b = a[1] //returns [4, 5, 6]
So essentially I would like this function to have two possible return types depending on context. I would like to do this without having to return a tuple/enum/etc. so that it just automatically infers the return types based on the dimensions of the matrix. Is this possible?
You're close, here's something to get you started.
I return an optional to avoid crashing from an index range error.
extension Array {
var bounds : (Int, Int) {
get {
return (0, self.count - 1)
}
}
}
extension Array where Element == Array<Int> {
subscript(first: Int, second: Int) -> Int? {
get {
guard !self.isEmpty else { return nil }
guard first <= bounds.1 else { return nil }
let it = self[first]
guard second <= it.bounds.1 else { return nil }
return it[second]
}
}
}
let one = [[1, 0], [3, 4]]
print(one[1, 0]) // => Optional(3)

How to create lazy combinations

My question is very simple, how do I make this code lazy:
/*
input: [
[1, 2],
[3, 4],
[5, 6]
]
output: [
[1, 3, 5],
[1, 3, 6],
[1, 4, 5],
[1, 4, 6],
[2, 3, 5],
[2, 3, 6],
[2, 4, 5],
[2, 4, 6],
]
*/
func combinations<T>(options: [[T]]) -> [[T]] {
guard let head = options.first else {
return [].map({ [$0] })
}
if options.count == 1 {
return head.map({ [$0] })
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return head.flatMap({ option in
return tailCombinations.map({ combination -> [T] in
return [option] + combination
})
})
}
The code above works to calculate the combinations, but it does so creating the entire array of arrays in memory.
What I need is to have it return something like LazySequence<Array<T>>, except the Swift type system doesn't let me do something that generic.
Any ideas how to achieve this and keep the functional style?
Ps.: I did think of another way to solve this problem with generators and keeping track of indexes, but I don't wanna keep track of any state, I want a pure functional (as in FP) solution.
Haskell does it by default, btw, and I'm looking for the same thing.
EDIT: I've managed to solve part of the problem, the type system, with AnyCollection
func combinations<T>(options: [[T]]) -> LazyCollection<AnyCollection<[T]>> {
guard let head = options.first else {
return AnyCollection([].lazy.map({ [$0] })).lazy
}
if options.count == 1 {
return AnyCollection(head.lazy.map({ [$0] })).lazy
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return AnyCollection(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})).lazy
}
But when I use the function, it loads the entire collection in memory, i.e., not lazy.
EDIT 2: Doing some more investigation, turns out the problem is with AnyCollection
// stays lazy
let x1 = head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})
// forces to load in memory
let x2 = AnyCollection(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
}))
Not sure how to solve this yet.
Here is what I came up with:
func combinations<T>(options: [[T]]) -> AnySequence<[T]> {
guard let lastOption = options.last else {
return AnySequence(CollectionOfOne([]))
}
let headCombinations = combinations(options: Array(options.dropLast()))
return AnySequence(headCombinations.lazy.flatMap { head in
lastOption.lazy.map { head + [$0] }
})
}
The main difference to this solution is that the recursive
call creates a sequence
of the first N-1 options, and then combines each element of
that sequence with each element of the last option. This is more
efficient because the sequence returned from the recursive call
is enumerated only once, and not once for each element that it is
combined with.
Other differences are:
There is no need to call .lazy on the AnySequence if that
sequence is already lazy. The return type is therefore "simplified"
to AnySequence<[T]>.
I have used CollectionOfOne to create a single-element sequence
for the empty array.
Treating the case options.count == 1 separately is not necessary
for the algorithm to work (but might be a possible performance
improvement).
A completely different approach is to define a custom collection type
which computes each combination as a function of the index, using
simple modulo arithmetic:
struct Combinations<T> : RandomAccessCollection {
let options: [[T]]
let startIndex = 0
let endIndex: Int
init(options: [[T]]) {
self.options = options.reversed()
self.endIndex = options.reduce(1) { $0 * $1.count }
}
subscript(index: Int) -> [T] {
var i = index
var combination: [T] = []
combination.reserveCapacity(options.count)
options.forEach { option in
combination.append(option[i % option.count])
i /= option.count
}
return combination.reversed()
}
}
No extra storage is needed and no recursion. Example usage:
let all = Combinations(options: [[1, 2], [3, 4], [5, 6]])
print(all.count)
for c in all { print(c) }
Output:
8
[1, 3, 5]
[1, 3, 6]
[1, 4, 5]
[1, 4, 6]
[2, 3, 5]
[2, 3, 6]
[2, 4, 5]
[2, 4, 6]
Testing with
let options = Array(repeating: [1, 2, 3, 4, 5], count: 5)
this collection-based method turned out to be faster then the
my above sequence-based method by a factor of 2.
I found one possible solution, but I'll leave this answer not accepted for a while to see if someone knows a better one.
func combinations<T>(options: [[T]]) -> LazySequence<AnySequence<[T]>> {
guard let head = options.first else {
return AnySequence([].lazy.map({ [$0] })).lazy
}
if options.count == 1 {
return AnySequence(head.lazy.map({ [$0] })).lazy
}
let tailCombinations = combinations(options: Array(options.dropFirst()))
return AnySequence(head.lazy.flatMap({ option in
return tailCombinations.lazy.map({ [option] + $0 })
})).lazy
}
The solution was to use AnySequence instead of AnyCollection.
I'm not sure why though, I'd still like to have the AnyCollection interface rather than AnySequence, since it provides me with a few more methods, like count.

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
}
}