10

I understand that in Swift 3 there have been some changes from typical C Style for-loops. I've been working around it, but it seems like I'm writing more code than before in many cases. Maybe someone can steer me in the right direction because this is what I want:

let names : [String] = ["Jim", "Jenny", "Earl"]
for var i = 0; i < names.count - 1; i+=1 {
    NSLog("%@ loves %@", names[i], names[i+1])
}

Pretty simple stuff. I like to be able to get the index I'm on, and I like the for-loop to not run if names.count == 0. All in one go.

But it seems like my options in Swift 3 aren't allowing me this. I would have to do something like:

let names : [String] = ["Jim", "Jenny", "Earl"]
if names.count > 0 {
    for i in 0...(names.count - 1) {
        NSLog("%@ loves %@", names[i], names[i+1])
    }
}

The if statement at the start is needed because my program will crash in the situation where it reads: for i in 0...0 { }

I also like the idea of being able to just iterate through everything without explicitly setting the index:

// Pseudocode
for name in names.exceptLastOne {
    NSLog("%@ loves %@", name, name.plus(1))
}

I feel like there is some sort of syntax that mixes all my wants, but I haven't come across it yet. Does anyone know of a way? Or at least a way to make my code more compact?

UPDATE: Someone suggested that this question has already been asked, citing a SO post where the solution was to use something to the degree of:

for (index, name) in names.enumerated {}

The problem with this when compared to Hamish's answer is that I only am given the index of the current name. That doesn't allow me to get the value at index without needing to do something like:

names[index + 1]

That's just one extra variable to keep track of. I prefer Hamish's which is:

for i in names.indices.dropLast() {
    print("\(names[i]) loves \(names[i + 1])")
}

Short, simple, and only have to keep track of names and i, rather than names, index, and name.

Kjell
  • 801
  • 7
  • 19
  • Possible duplicate of [swift for loop: for index, element in array?](http://stackoverflow.com/questions/24028421/swift-for-loop-for-index-element-in-array) – Sergey Kalinichenko Dec 04 '16 at 23:39
  • 2
    `for name in names.dropLast() {` – Leo Dabus Dec 04 '16 at 23:41
  • 3
    @LeoDabus But the OP needs to access two different objects from the array during each iteration. – rmaddy Dec 04 '16 at 23:45
  • 1
    `for (index, name) in names.dropLast().enumerated() {` – Leo Dabus Dec 04 '16 at 23:47
  • @LeoDabus Post an answer. – rmaddy Dec 04 '16 at 23:49
  • @rmaddy Hamish already posted a similar approach – Leo Dabus Dec 04 '16 at 23:51
  • Note that you can (and indeed should) drop the explicit type annotation from `names` and use `print` with string interpolation rather than `NSLog`. – Hamish Dec 04 '16 at 23:59
  • @Hamish not that it has to do with the original question, but why should I use print rather than NSLog? I was using print for a bit, but then for my macOS app, I would sometimes get into certain files and try print, but I ended up getting a popup saying I couldn't print (while running the app). So in order to not have to think about it anymore, I got back into the habit of NSLog. Also, why drop explicit type annotation? I thought doing that speeds up compiler. – Kjell Dec 05 '16 at 03:16
  • @Kjell I agree that there are some cases where you need to use `NSLog`, and your situation may well be one of them – but in general you should prefer to use `print`, as `NSLog` only works with (bridgeable to) Objective-C types (e.g if you try and `NSLog` a custom Swift type, you'll get a compiler error). In addition, `print` allows you to customise the output of a given custom type through conformance to `Custom(Debug)StringConvertible`. Omitting type annotations makes your code cleaner and easier to read – although there are indeed corner cases where they're needed to assist the compiler. – Hamish Dec 05 '16 at 10:29

1 Answers1

14

One option would be to use dropLast() on the array's indices, allowing you to iterate over a CountableRange of all but the last index of the array.

let names = ["Jim", "Jenny", "Earl"]

for i in names.indices.dropLast() {
    print("\(names[i]) loves \(names[i + 1])")
}

If the array has less than two elements, the loop won't be entered.

Another option would be to zip the array with the array where the first element has been dropped, allowing you to iterate through the pairs of elements with their successor elements:

for (nameA, nameB) in zip(names, names.dropFirst()) {
    print("\(nameA) loves \(nameB)")
}

This takes advantage of the fact that zip truncates the longer of the two sequences if they aren't of equal length. Therefore if the array has less than two elements, again, the loop won't be entered.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Wow, I didn't know about zip! That's awesome! Also didn't know about indicies.dropLast(). Will definately make use of that as I like having a reference to the index. – Kjell Dec 05 '16 at 03:09