Is a range of Ints actually an array?

If I have a range of type Int, i.e., (2…100) is that range in fact an array of Ints?

No, it's just a pair of numbers. But Range<Int> does conform to Collection so in many cases it can be used as if it was an Array<Int>.

3 Likes

I am following a book that proposed the following challenge:

Print all the even numbers from 2 to 100

I wrote the following as my solution:

let numericRange = 2...100

for eachNumber in numericRange {
    if eachNumber % 2 == 0 {
        print(eachNumber)
    }
}

The book proposed the following two solutions (in Python):

Solution #1
def print_numbers_version_one(): 
    number = 2

    while number <= 100:
        if number % 2 == 0: # If number is even, print it:
            print(number)
        number += 1
Solution #2
def print_numbers_version_two(): 
    number = 2

    while number <= 100: 
        print(number)

        # Increase number by 2, which, by definition, 
        # is the next even number:
        number += 2

The book states that Solution #2 "runs faster". My question is, is MY solution acceptable and does it run equally fast or faster than the two solutions proposed in the book?

Also, my programmer friend said that the range in my code is an array, making it inefficient. Is that true??

The range is not an array, in allows any type that conforms to the Sequence protocol. It appears that your solution with the for-in loop produces shorter assembly than either of the while-based options (Godbolt Link), but I'm not sure whether that means it's actually faster or not.

The first Python solution you gave runs slower than the second, because it's incrementing number one at a time, through all the numbers, even the ones we're sure aren't even. We then have to waste time checking that.

The second solution fixes that by just increment by two, skipping over odd numbers, and guaranteeing that every value of number is even without needing to check it explicitly.

Your solution is equivalent to the first. A Swift equivalent of the second solution might look like:

for eachNumber in stride(from: 2, through: 100, by: +2) {
    print(eachNumber)
}

Which uses stride(from:through:by:). It produces a StrideThrough<Int>, which is similar to a Range<Int>, but also stores a stride to increment by each time.

4 Likes

@mango to answer your original question more generally:

There's 3 ways to find out the type of an expression:

  1. If you're using Xcode, you can option click it, and it will show you the inferred type.

  2. You can print it at runtime with: print(type(of: 2...100)) # => ClosedRange<Int>

  3. In the niche case that you don't want to run your program, and you're not using Xcode or another editor with LSP integration, you can force a type error and see the message:

    // error: cannot convert value of type 'ClosedRange<Int>' to specified type 'Void'
    let definitelyNotVoid: Void = 2...100
    
5 Likes

This is plainly false.

But, I understand where the confusion might stem from. In Swift (as in many modern languages), arrays conform to a series of protocols/interfaces that are more general than arrays.

Originally (as in in early computer history), arrays were contiguous section of memory, allocated to be able to store several values, back-to-back, contiguously in e.g. RAM. The memory would have to be instantiated, and filled with values. But conceptually, an array were always a just "list" or "collection" of "things".

But there are also other kinds of "list of things" that aren't contiguously allocated spans of memory, but still encapsulates the idea of a "list of things". Moderns computer languages abstract this notion into a family of related protocols/interfaces. Swift, has Collection and Sequence. A sequence is anything that can be iterated through. A collection is anything that you can look into and extract a value from, given an index. Arrays are both collections and sequences.

In Swift, when you write for ... in ... you can iterate over anything that is a Sequence, not just arrays.

A Range<Int> is just a pair of values, the upper and lower bound. Even though it just occupies those two numbers in memory, it conceptually represents the entire range of values from the lower bound to the upper bound. Therefore Range implements Sequence, and is eligible for iteration, just as if you instead used an Array.

But of you iterate over a range of millions of numbers, there is no need to allocate a span of memory millions of bytes long, set each byte to an increasing number from 1... and then read each memory location, one-by-one. Instead you can just create a counter, start from the first, and keep increasing it until you reach the end. In that way, ranges are much more efficient than arrays.

But in many cases Range<Int> looks like an Array<Int>, because they both get all the functionalities of sequences and collections.

5 Likes