Iterate over collection two at a time in Swift
You can use a progression loop called stride(to:, by:) to iterate over your elements every n elements:
let array = Array(1...5)
let pairs = stride(from: 0, to: array.endIndex, by: 2).map {
(array[$0], $0 < array.index(before: array.endIndex) ? array[$0.advanced(by: 1)] : nil)
} // [(.0 1, {some 2}), (.0 3, {some 4}), (.0 5, nil)]
print(pairs) // "[(1, Optional(2)), (3, Optional(4)), (5, nil)]\n"
To iterate your collection subsequences instead of tuples:
extension Collection {
func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
sequence(state: startIndex) { start in
guard start < self.endIndex else { return nil }
let end = self.index(start, offsetBy: maxLength, limitedBy: self.endIndex) ?? self.endIndex
defer { start = end }
return self[start..<end]
}
}
}
let array = Array(1...5)
for subsequence in array.unfoldSubSequences(limitedTo: 2) {
print(subsequence) // [1, 2] [3, 4] [5]
}
This would work on any kind of collection:
let string = "12345"
for substring in string.unfoldSubSequences(limitedTo: 2) {
print(substring) // "12" "34" "5"
}
Iterating with for .. in on a changing collection
The documentation for IteratorProtocol says "whenever you use a for-in loop with an array, set, or any other collection or sequence, you’re using that type’s iterator." So, we are guaranteed that a for in
loop is going to be using .makeIterator()
and .next()
which is defined most generally on Sequence
and IteratorProtocol
respectively.
The documentation for Sequence says that "the Sequence
protocol makes no requirement on conforming types regarding whether they will be destructively consumed by iteration." As a consequence, this means that an iterator for a Sequence
is not required to make a copy, and so I do not think that modifying a sequence while iterating over it is, in general, safe.
This same caveat does not occur in the documentation for Collection, but I also don't think there is any guarantee that the iterator makes a copy, and so I do not think that modifying a collection while iterating over it is, in general, safe.
But, most collection types in Swift are struct
s with value semantics or copy-on-write semantics. I'm not really sure where the documentation for this is, but this link does say that "in Swift, Array
, String
, and Dictionary
are all value types... You don’t need to do anything special — such as making an explicit copy — to prevent other code from modifying that data behind your back." In particular, this means that for Array
, .makeIterator()
cannot hold a reference to your array because the iterator for Array
does not have to "do anything special" to prevent other code (i.e. your code) from modifying the data it holds.
We can explore this in more detail. The Iterator
type of Array
is defined as type IndexingIterator<Array<Element>>
. The documentation IndexingIterator says that it is the default implementation of the iterator for collections, so we can assume that most collections will use this. We can see in the source code for IndexingIterator
that it holds a copy of its collection
@frozen
public struct IndexingIterator<Elements: Collection> {
@usableFromInline
internal let _elements: Elements
@usableFromInline
internal var _position: Elements.Index
@inlinable
@inline(__always)
/// Creates an iterator over the given collection.
public /// @testable
init(_elements: Elements) {
self._elements = _elements
self._position = _elements.startIndex
}
...
}
and that the default .makeIterator()
simply creates this copy.
extension Collection where Iterator == IndexingIterator<Self> {
/// Returns an iterator over the elements of the collection.
@inlinable // trivial-implementation
@inline(__always)
public __consuming func makeIterator() -> IndexingIterator<Self> {
return IndexingIterator(_elements: self)
}
}
Although you might not want to trust this source code, the documentation for library evolution claims that "the @inlinable
attribute is a promise from the library developer that the current definition of a function will remain correct when used with future versions of the library" and the @frozen
also means that the members of IndexingIterator
cannot change.
Altogether, this means that any collection type with value semantics and an IndexingIterator
as its Iterator
must make a copy when using using for in
loops (at least until the next ABI break, which should be a long-way off). Even then, I don't think Apple is likely to change this behavior.
In Conclusion
I don't know of any place that it is explicitly spelled out in the docs "you can modify an array while you iterate over it, and the iteration will proceed as if you made a copy" but that's also the kind of language that probably shouldn't be written down as writing such code could definitely confuse a beginner.
However, there is enough documentation lying around which says that a for in
loop just calls .makeIterator()
and that for any collection with value semantics and the default iterator type (for example, Array
), .makeIterator()
makes a copy and so cannot be influenced by code inside the loop. Further, because Array
and some other types like Set
and Dictionary
are copy-on-write, modifying these collections inside a loop will have a one-time copy penalty as the body of the loop will not have a unique reference to its storage (because the iterator will). This is the exact same penalty that modifying the collection outside the loop with have if you don’t have a unique reference to the storage.
Without these assumptions, you aren't guaranteed safety, but you might have it anyway in some circumstances.
Edit:
I just realized we can create some cases where this is unsafe for sequences.
import Foundation
/// This is clearly fine and works as expected.
print("Test normal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
print(i)
}
}
/// This is also okay. Reassigning `x` does not mutate the reference that the iterator holds.
print("Test reassignment")
for _ in 0...10 {
var x: NSMutableArray = [0,1,2,3]
for i in x {
x = []
print(i)
}
}
/// This crashes. The iterator assumes that the last index it used is still valid, but after removing the objects, there are no valid indices.
print("Test removal")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x {
x.removeAllObjects()
print(i)
}
}
/// This also crashes. `.enumerated()` gets a reference to `x` which it expects will not be modified behind its back.
print("Test removal enumerated")
for _ in 0...10 {
let x: NSMutableArray = [0,1,2,3]
for i in x.enumerated() {
x.removeAllObjects()
print(i)
}
}
The fact that this is an NSMutableArray
is important because this type has reference semantics. Since NSMutableArray
conforms to Sequence
, we know that mutating a sequence while iterating over it is not safe, even when using .enumerated()
.
What is the most effective way to iterate over a date range in Swift?
If I understand your question correctly, the user will check off some weekdays and provide a duration as a number of days.
Assuming you have the selected weekdays in an array and the duration, you can get the list of matching dates as follows:
// User selected weekdays (1 = Sunday, 7 = Saturday)
var selectedWeekdays = [2, 4, 6] // Example - Mon, Wed, Fri
var duration = 10 // Example - 10 days
let calendar = Calendar.current
var today = Date()
let dateEnding = calendar.date(byAdding: .day, value: duration, to: today)!
var matchingDates = [Date]()
// Finding matching dates at midnight - adjust as needed
let components = DateComponents(hour: 0, minute: 0, second: 0) // midnight
calendar.enumerateDates(startingAfter: today, matching: components, matchingPolicy: .nextTime) { (date, strict, stop) in
if let date = date {
if date <= dateEnding {
let weekDay = calendar.component(.weekday, from: date)
print(date, weekDay)
if selectedWeekdays.contains(weekDay) {
matchingDates.append(date)
}
} else {
stop = true
}
}
}
print("Matching dates = \(matchingDates)")
How to loop through two arrays with different amount of items? Swift
You can simply do,
var i = 0
for _ in 0..<min(alienArray.count, humanArray.count) {
print(humanArray[i].name, alienArray[i].numberOfLegs)
i += 1
}
print(humanArray[i...].compactMap({ $0.name }).joined(separator: " "))
print(alienArray[i...].compactMap({ $0.numberOfLegs }).joined(separator: " "))
Iterating over multiple arrays with ForEach SwiftUI
Here is possible approach (tested & worked with Xcode 11.3.1)
ForEach(Array(zip(heights, labels)), id: \.0) { item in
VStack {
Text("\(item.0)")
Text(item.1)
}
}
Is there a way I could iterate over elements in Swift while using method names?
Just in case you don't fully understand the motivation behind this overload of reversed
returning a ReversedCollection
instead of an Array
, a ReversedCollection
is just a "reversed view" of your original array. It is not a reversed copy of the original array. This is to save time and space, like a "lazy" collection. See this post for more details.
This is why you need the Array(...)
initialiser to turn the reversed collection back into an array. You are opting out of the laziness.
On the other hand, there is another overload of reversed
that returns an Array
directly. Normally this overload is not selected because it is defined in a less specific type - Sequence
, as opposed to Array
. You need to give enough information about the type to use this overload:
let x: [Int] = [1, 2, 2, 3, 3, 3, 1].reversed()
iterate through two custom arrays and set values if variables are equal Swift
Basically, you need only one for
/forEach
loop to achieve what you want:
var fbTweetArray: [FBTweet] = [
FBTweet(tweetId: 1, tweetText: "1"),
FBTweet(tweetId: 2, tweetText: "2"),
FBTweet(tweetId: 3, tweetText: "3")
]
var statusArray: [Status] = [
Status(statusId: 2, statusText: nil),
Status(statusId: 1, statusText: nil),
Status(statusId: 3, statusText: nil)
]
fbTweetArray.forEach { tweet in
if let index = statusArray.index(where: { $0.statusId == tweet.tweetId }) {
statusArray[index].statusText = tweet.tweetText
}
}
print(statusArray.map { $0.statusText }) // [Optional("2"), Optional("1"), Optional("3")]
Note, that your id
s in both structures can be nil
. To handle this situation (if both id is nil - objects are not equal) you can write custom ==
func:
struct Status {
var statusId: Int? //set
var statusText: String? //no value
static func == (lhs: Status, rhs: FBTweet) -> Bool {
guard let lhsId = lhs.statusId, let rhsId = rhs.tweetId else { return false }
return lhsId == rhsId
}
}
...
// rewrite .index(where: ) in if condition
if let index = statusArray.index(where: { $0 == tweet }) { ... }
Also, there is some pro-tip. If you adopt your structs to Hashable
protocol, you will be able to place FBTweet
s and Status
es into Set
structure. The benefits of that:
If you instead store those objects in a set, you can theoretically
find any one of them in constant time (O(1)) — that is, a lookup on a
set with 10 elements takes the same amount of time as a lookup on a
set with 10,000.
You can find more in-depth info about it in a new great article by NSHipster.
Iterating over multiple arrays in Swift
I managed to figure it out with some help from a friend:
var newArray: [String] = []
for (index, array1) in array1.enumerated() {
newArray.append("\(index + 1). \(array1)(\(array2[index]))")
}
return newArray
Thanks!
Related Topics
Call a Method from a String in Swift
Get Integer Value from String in Swift
What Does a Module Mean in Swift
Failing Cast in Swift from Any? to Protocol
Why Constant Constraints the Property from a Structure Instance But Not the Class Instance
Cannot Assign Property in Method of Struct
How to Encode a String to Base64 in Swift
Can Swift Convert a Class/Struct Data into Dictionary
How to Create Generic Protocols in Swift
Using a Type Variable in a Generic
How to Convert Double to Int in Swift