Swift: For Loop End Condition Evaluated Only Once? - swift

I am attempting to understand how Swift handles for-in loops.
Overview: we are iterating over the rows of an NSOutlineView. If a condition is met, we expand the item, which obviously changes the overall row count of the outlineView.
Pre-Condition: the OutlineView has 5 "root" items. Each of those has 5 child items.
Example
final class anOutlineView: NSOutlineView
{
override func reloadData()
{
super.reloadData()
for i in 0 ..< self.numberOfRows
{
// Assume we expand the item at row 0, which increases
// the overall outlineView row count from 5 to 10.
}
}
}
In this approach, the loop stops when i == 4. I assume that's because Swift evaluates the range only once, the first time it encounters it? Is there a way to change that behavior so that the conditions are re-evaluated each time through the loop, like a traditional for loop?
Replacing the for loop with a while loop obviously works and is a fine solution. I'm simply trying to understand the nuances of Swift because this behavior is not what I expected. In Objective-C, the for loop conditions were evaluated on each iteration and it was a well-known performance optimization to refrain from calling self.property in loop conditions (unless a good reason existed, as it does in this case.)

0 ..< self.numberOfRows is a Range and in particular a Sequence. Iterating over a sequence is done by creating an iterator, and then calling its next() method until the iterator is exhausted, compare IteratorProtocol:
Whenever you use a for-in loop with an array, set, or any other collection or sequence, you’re using that type’s iterator. Swift uses a sequence’s or collection’s iterator internally to enable the for-in loop language construct.
So
for i in 0 ..< self.numberOfRows {
...
}
is equivalent to
let range = 0 ..< self.numberOfRows
var it = range.makeIterator()
while let i = it.next() {
...
}
Modifying numberOfRows during the iteration does not mutate the range (which is a value type) or the iterator, and therefore does not affect the number of iterations.

Related

Filtering and reducing lazy structures

I would like to filter a lazy structure and then reduce it using Swift language.
func main() -> () {
let result = (1...)
.lazy
.filter { $0 < 3 }
.reduce(0, {$0 + $1})
return print(
result
)
}
main()
This code compiles; however, the program doesn't execute in a proper way (takes too long). The printed result on the screen should be 3.
Is there a way to accomplish this goal ?
The issue is not that your sequence is lazy, it's that your sequence is infinite. You might be looking for the sequence method. Example:
let s = sequence(first: 0) {
$0 > 3 ? nil : $0 + 1
}
let result = s.reduce(0) { $0 + $1 }
print(result) // 10
s is lazy and is potentially infinite but not actually infinite, because the method that generates the next item in the series does an early exit by returning nil when the sequence goes past 3. This is similar to your filter except that it is not a filter, it's a stopper. You can use any condition you like here to generate the stopper.
To get the program to terminate, you would need to add an upper limit on your original sequence (1...).
As it's written, you have an infinite sequence of numbers, starting at 1. The following operators — the filter, in particular — have no way of "knowing" that they can discard the rest of the sequence once they pass 3. They have to process the entire infinite sequence, filtering out all but the first two elements, before your reduce can produce a final result and you can print it out. (In practice, you'll eventually overflow Int, so the program would terminate then, but that's not really a good thing to rely on.)
If you don't want to change the original (1...), you can approximate the same behavior by swapping out your filter with a prefix. A filter has to look at every element; a prefix can "know" that it stops after a certain number of elements. For example, this runs very quickly and prints out 3:
let result = (1...)
.lazy
.prefix(2)
.reduce(0) {$0 + $1}

Is first(where:) Method always O(n) or it can be O(1) with usage of Set or Dictionary?

I like to know if I use Set instead of Array can my method of first(where:) became Complexity:O(1)?
Apple says that the first(where:) Method is O(n), is it in general so or it depends on how we use it?
for example look at these two ways of coding:
var numbers: [Int] = [Int]()
numbers = [3, 7, 4, -2, 9, -6, 10, 1]
if let searchResult = numbers.first(where: { value in value == -2 })
{
print("The number \(searchResult) Exist!")
}
else
{
print("The number does not Exist!")
}
and this:
var numbers: Set<Int> = Set<Int>()
numbers = [3, 7, 4, -2, 9, -6, 10, 1]
if let searchResult = numbers.first(where: { value in value == -2 })
{
print("The number \(searchResult) Exist!")
}
else
{
print("The number does not Exist!")
}
can we say that in second way Complexity is O(1)?
It's still O(n) even when you use a Set. .first(where:) is defined on a sequence, and it is necessary to check the items in the sequence one at a time to find the first one that makes the predicate true.
Your example is simply checking if the item exists in the Set, but since you are using .first(where:) and a predicate { value in value == -2} Swift will run that predicate for each element in the sequence in turn until it finds one that returns true. Swift doesn't know that you are really just checking to see if the item is in the set.
If you want O(1), then use .contains(-2) on the Set.
I recommend to learn more about Big-O notation. O(1) is a strict subset of O(n). Thus every function that is O(1) is also in O(n).
That said, Apple’s documentation is actually misleading as it does not take the complexity of the predicate function into account. The following is clearly O(n^2):
numbers.first(where: { value in numbers.contains(value + 42) })
Both Set and Dictionary conform to the Sequence protocol, which is the one that exposes the first(where:) function. And this function has the following requirement, taken from the documentation:
Complexity: O(n), where n is the length of the sequence.
Now, this is the upper limit of the function complexity, it might well be that some sequences optimize the search based on their data type and the storage details.
Bottom line: you need to reach the documentation for a particular type if you want to know more about the performance of some feature, however if you're only circulating some protocol references, then you should assume the "worst" - aka what's in the protocol documentation.
This is the implementation of the first(where:) function in the sequence:
/// - Complexity: O(*n*), where *n* is the length of the sequence.
#inlinable
public func first(
where predicate: (Element) throws -> Bool
) rethrows -> Element? {
for element in self {
if try predicate(element) {
return element
}
}
return nil
}
From the Swift Source Code on the Github
As you can see, It's a simple for loop and the complexity is O(n) (assuming the predicate complexity is 1 🤷🏻‍♂️).
The predicate executes n times. So the worst case is O(n)
The Set has not an overload for this function (since it is nonsense and there will be nothing more than the first one in a Set). If you know about the sequence and you are just looking for a value (not a predicate), just use contains or firstIndex(of:). These two have overloads with the complexity of O(1)
From the Swift Source Code on the Github

In swift, is there a way to only check part of an array in a for loop (with a set beginning and ending point)

So lets say we have an array a = [20,50,100,200,500,1000]
Generally speaking we could do for number in a { print(a) } if we wanted to check the entirety of a.
How can you limit what indexes are checked? As in have a set beginning and end index (b, and e respectively), and limit the values of number that are checked to between b and e?
For an example, in a, if b is set to 1, and e is set to 4, then only a1 through a[4] are checked.
I tried doing for number in a[b...e] { print(number) }, I also saw here someone do this,
for j in 0..<n { x[i] = x[j]}, which works if we want just a ending.
This makes me think I can do something like for number in b..<=e { print(a[number]) }
Is this correct?
I'm practicing data structures in Swift and this is one of the things I've been struggling with. Would really appreciate an explanation!
Using b..<=e is not the correct syntax. You need to use Closed Range Operator ... instead, i.e.
for number in b...e {
print(a[number])
}
And since you've already tried
for number in a[b...e] {
print(number)
}
There is nothing wrong with the above syntax as well. You can use it either way.
An array has a subscript that accepts a Range: array[range] and returns a sub-array.
A range of integers can be defined as either b...e or b..<e (There are other ways as well), but not b..<=e
A range itself is a sequence (something that supports a for-in loop)
So you can either do
for index in b...e {
print(a[index])
}
or
for number in a[b...e] {
print(number)
}
In both cases, it is on you to ensure that b...e are valid indices into the array.

Why can a Swift constant be used as a loop index?

I know this Swift code is valid:
let index = 0
for index in (1...9) {
print(index) // index changes value from 1 to 9
}
But then if you say
index += index
you get the error
"Cannot assign to value: 'index' is a 'let' constant"
So, if index is a constant why is it ok to use it in the loop where its value will change?
So, if index is a constant why is it ok to use it in the loop where its value will change?
You could think of index as being newly created and initialized each time through the loop, just as a variable declared inside the loop's body would be. It makes sense to use let here because normally you don't change the loop counter inside the loop.
Update: After the edit, the code makes more sense, and pkamb's comment really is more clear: the index that you declare with let index = 0 is a different variable from the index used in the for loop.

Does this sorting algorithm exist? (implemented in Swift)

This might be a bad question but I am curious.
I was following some data structures and algorithms courses online, and I came across algorithms such as selection sort, insertion sort, bubble sort, merge sort, quick sort, heap sort.. They almost never get close to O(n) when the array is reverse-sorted.
I was wondering one thing: why are we not using space in return of time?
When I organise something I pick up one, and put it where it belongs to. So I thought if we have an array of items, we could just put each value to the index with that value.
Here is my implementation in Swift 4:
let simpleArray = [5,8,3,2,1,9,4,7,0]
let maxSpace = 20
func spaceSort(array: [Int]) -> [Int] {
guard array.count > 1 else {
return array
}
var realResult = [Int]()
var result = Array<Int>(repeating: -1, count: maxSpace)
for i in 0..<array.count{
if(result[array[i]] != array[i]){
result[array[i]] = array[i]
}
}
for i in 0..<result.count{
if(result[i] != -1){
realResult.append(i)
}
}
return realResult
}
var spaceSorted = [Int]()
var execTime = BenchTimer.measureBlock {
spaceSorted = spaceSort(array: simpleArray)
}
print("Average execution time for simple array: \(execTime)")
print(spaceSorted)
Results I get:
Does this sorting algorithm exist already?
Is this a bad idea because it only takes unique values and loses the duplicates? Or could there be uses for it?
And why can't I use Int.max for the maxSpace?
Edit:
I get the error below
error: Execution was interrupted.
when I use let maxSpace = Int.max
MyPlayground(6961,0x7000024af000) malloc: Heap corruption detected,
free list is damaged at 0x600003b7ebc0
* Incorrect guard value: 0 MyPlayground(6961,0x7000024af000) malloc: * set a breakpoint in malloc_error_break to debug
Thanks for the answers
This is an extreme version of radix sort. Quoted from Wikipedia:
radix sort is a non-comparative sorting algorithm. It avoids comparison by creating and distributing elements into buckets according to their radix. For elements with more than one significant digit, this bucketing process is repeated for each digit, while preserving the ordering of the prior step, until all digits have been considered. For this reason, radix sort has also been called bucket sort and digital sort.
In this case you choose your radix as maxSpace, and so you don't have any "elements with more than one significant digit" (from quote above).
Now, if you would use a Hash Set data structure instead of an array, you would actually not need to really allocate the space for the whole range. You would still keep all the loop iterations though (from 0 to maxSpace), and it would check whether the hash set contains the value of i (the loop variable), and if so, output it.
This can only be an efficient algorithm if maxSpace has the same order of magnitude as the number of elements in your input array. Other sorting algorithms can sort with O(nlogn) time complexity, so for cases where maxSpace is much greater than nlogn, the algorithm is not that compelling.