For-in loop combining sequence and range

Hi community,

I had this idea to combine iterating both sequence and range in a single for statement.

For example,

    var 2DArrray = [[Int]]()
    var dictionary = [Int, [Int]]()
    var iterator = 0
    for element in 2DArray {
       dictionary.updateValue(element, forKey: iterator)
       itr = itr + 1
    }

Can we introduce something like below,

var 2DArrray = [[Int]]()
var dictionary = [Int, [Int]]()
for element in 2DArray, iterator in 0..<2DArray.count {
    dictionary.updateValue(element, forKey: iterator)
}

We can also consider the below cases,

  1. Only first iteration ( "element in 2DArray" in above example) for iteration condition and anything after comma is just for convenience

  2. We can also have OR, AND etc (||, &&) in the multiple iterators like below

    var 2DArrray = [Int]
    var dictionary = Int, [Int]
    for ( element in 2DArray) ||( iterator in 0..<2DArray.count) {
    dictionary.updateValue(element, forKey: iterator)
    }

I am not sure if this elegant, or there are already better ways to handle cases like above.

Unless I misunderstood, this is what enumerated does.

3 Likes

And if your range is more arbitrary, you can use zip.

for (element, iterator) in zip(2DArray, 2DArray.indices) {
    dictionary.updateValue(element, forKey: iterator)
}

Though I still feels a bit weird writing this since 2DArray is not a valid identifier.

5 Likes

It’s probably not advisable to call your variable “iterator” when it’s actually an index.

Use zip, as Latuna says. “enumerated” always starts from 0, and your indexes may not.

5 Likes

Although the example may not be compelling, imho it would be nice if several loops could be written with a single for:

for customer in customers, order in customer.orders { ...

instead of

for customer in customers {
   for order in customer.orders { ....

It could even be possible to use where, which reminds me a lot on SQL.

It's not a feature that I really miss, but looking at if and while, the restriction in for seems to be an exception.

6 Likes

enumerated doesn’t give an index but gives you the index offset. So it happens to work for Array and collections where the indices are 0..<count. So it is usually better to use zip with the collection and its indices so it works with any type.

1 Like

If you only need to work with orders during your processing, without needing to know an order's corresponding customer, what's wrong with:

for customers.map({ $0.orders }).joined() { /*...*/ }

What are you actually trying to do? Your initial code is completely unnecessary.

for index in twoDArray.indices {
    let innerArray = twoDArray[index]  // innerArray's type is [Int]
    // do whatever...
}

There is no need for a dictionary that maps an index from a collection to its corresponding element; the whole point of a Collection is that it provides that operation for you already!

("Iterator" is especially wrong here because we use it for a different, yet related, concept in Swift.)

1 Like

I think what @Tino is referring to is more general than that. Here's an example:

for i in 0...5 {
    for j in 0...i {
        print(i, j)
    }
}

With his proposed syntax we could write it this way:

for i in 0...5, j in 0...i {
    print(i, j)
}

Apart for the reduced code length, we now gain the possibility to have i and j as variables instead of constants using var or even an immediate filter using where:

for i in 0...5, j in 0...5 where j <= i {
    print(i, j)
}

I think this is an interesting idea. To get the same result using map, flatMap, reduce et simila, you would need something like this for the first example:

for (i, j) in (0...5).flatMap({ i in (0...i).map { (i, $0) } }) {
    print(i, j)
}

and this for last one:

for (i, j) in (0...5).flatMap({ i in (0...5).map { (i, $0) } }).filter(>=) {
    print(i, j)
}

which of course are more difficult to apply for a developer not experienced with higher-order functions. I personally took 5 minutes to get the last example right.

3 Likes

So what we would want is something like zip, but the entity is a collection that is the Cartesian product of the input collections. (Like zip, it may be most effective when we get variadic generics.) The new collection's elements are tuples that represent every combination of one element from the first source and one element from the second.

I did write up a brief sample type summary here, but I erased it and suggest that you should just search for "cartesian product" on these forums instead.

(If we had intermediate protocols to represent finite sequences and re-visitable sequences, we wouldn't need Collection conformance. At most one source wouldn't have to conform to FiniteSequence & PersistentSequence. The product sequence would conform to FiniteSequence if all sources do and to PersistentSequence if all sources do.)

That wouldn't compile, would it? ;-)
It's quite clear that the functional approach is more complicated* — and that may not only be the case for the writer, but also for the reader (and the compiler as well).
Giving for some of the powers other statements had for a long time, we could also address the issues SE-0231 tried to solve (SE-0231 — Optional iteration - #108 by Michael_Ilseman).

* Actually, I think flatMap would be the best alternative

2 Likes

Apologies I am noob and sure will be careful with wordings and variables names.

@Braden_Scothern @sjavora @Lantua

Sure, all the examples are great but I intention was to almost like what @Tino suggested giving powers to for loop and my simple example didn't convey that.

Please consider the below points

  1. Both enumerated and zip are great tools, but both are tied in some ways. Example, enumerated needs to start from 0 index while zip is great it has this limiting factor
    " If the two sequences passed to zip(_:_:) are different lengths, the resulting sequence is the same length as the shorter sequence"
  2. With greater complexity, functional approach does get complicated,

What I envision is that with this addition for-in loop can

  1. handle multiple range/sequences decoupled from each other,
  2. define custom ranges along with accompanying sequence iteration on which it doesnt relate to directly,
  3. simplify dealing custom/complex ranges due to point 2,
  4. give more power to for like @Tino said,

You are perfectly fine! There is no need to apologize for being a “noob”. We all just want to help you find a good solution and/or help you solve your problem in a good way.

I agree with @Tino and what you have stated about improving for loops. I feel it fits naturally with how if/switch statements are formed. It is purely additive to the language from what I can tell so it doesn’t have source compatibility issues.

The only issue I can see with this idea to expand if is that this is more syntactic sugar. Those are generally the hardest pitches to move through evolution with where Swift is at in development.

1 Like

I'm still trying to figure out what you and @Tino actually want to do.

For [1], would the "Cartesian product" idea I already mentioned work? We would need to add it to the Standard library.

(The Cartesian product won't work for @Tino 's idea since the second aspect is dependent on the first. For that case, use customers.flatMap({ $0.orders }) if you don't need each order's corresponding customer, and

customers.flatMap { c in
    c.orders.map { o in
        (c, o)
    }
}

if you do.)

And I don't understand what [2] means. (Which means I can't comment on [3] yet.) I showed with the functional programming that we don't need [4].

1 Like

Okay. I get your point.

Point 2 I meant, the ability to define custom ranges - I meant we can start the range from any index to any other index, a direct consequence of point 1 decoupling.
So, I thought points 1 and 2 will simplify handling ranges for something on not so simple sequences/dictionaries etc. in for-in loop statement itself.

And the example you have shown is very much valid and as @Braden_Scothern said, what I was suggesting was purely additive.