1

I'm currently converting C++ code to Swift and I've gotten stuck on one part. The parameter passed into the function is a string and the area where I'm stuck is when attempting to set a variable based on the second to last character of a string to check for a certain character.

The error shows up on this line:

line[i-1]

I've tried casting this value to an Int but this didn't work:

Int(line[i - 1])

I've also tried to see if the string's startIndex function which takes a Int would work but it didn't:

line.startIndex[i - 1]

Here is the full function:

func scanStringForSpecificCharacters(line: String){

    var maxOpen: Int = 0;
    var minOpen: Int = 0;

    minOpen = 0;
    maxOpen = 0;
    var i = 0
    while i < line.characters.count {
        for character in line.characters {

            //var c: Character = line[i];
            if character == "(" {
                maxOpen += 1;
                if i == 0 || line[i - 1] != ":" {
                    minOpen += 1;
                }
            }
            else if character == ")"{
                minOpen = max(0,minOpen-1);
                if i == 0 || line[i-1] != ":"{
                    maxOpen -= 1;
                }
                if maxOpen < 0{
                    break;
                }
            }
        }

        if maxOpen >= 0 && minOpen == 0{
            print("YES")
        }else{
            print("NO")
        }
    }
}
MathewS
  • 2,267
  • 2
  • 20
  • 31
Laurence Wingo
  • 3,912
  • 7
  • 33
  • 61
  • 2
    `line.characters[i-1]` maybe – JuicyFruit Jan 06 '17 at 22:01
  • Xcode throws the error 'Cannot subscript a value of type 'String.characterView' with an index of type 'Int''. – Laurence Wingo Jan 06 '17 at 22:03
  • Even tried line.characters.count but this didn't work either. – Laurence Wingo Jan 06 '17 at 22:04
  • 1
    Take a look at [How does String.Index work in Swift 3](http://stackoverflow.com/questions/39676939/how-does-string-index-work-in-swift-3) – you need to use `index(before:)` on the string. – Hamish Jan 06 '17 at 22:05
  • 1
    yeah, Hamish is right, `line.characters.index(before: i)` – JuicyFruit Jan 06 '17 at 22:06
  • 3
    What are you actually trying to achieve? What does your function compute? Is the nested loop intentional? – There might be a better approach in Swift. – Martin R Jan 06 '17 at 22:09
  • I'm not experienced in C++ but I thought it would be simple to convert this function to swift by making adjustments that I'm familiar with in swift: https://turjachaudhuri.wordpress.com/2013/12/15/facebook-hacker-cup-2013-balanced-smileys/ – Laurence Wingo Jan 06 '17 at 22:19

4 Answers4

3

Strings in Swift aren't indexed collections and instead you can access one of four different views: characters, UTF8, UTF16, or unicodescalars.

This is because Swift supports unicode, where an individual characters may actually be composed of multiple unicode scalars.

Here's a post that really helped me wrap my head around this: https://oleb.net/blog/2016/08/swift-3-strings/

Anyway, to answer you question you'll need to create an index using index(after:), index(before:), or index(_, offsetBy:).

In your case you'd want to do something like this:

line.index(line.endIndex, offsetBy: -2) // second to last character

Also, you'll probably find it easier to iterate directly using a String.Index type rather than Int:

let line = "hello"

var i = line.startIndex
while i < line.endIndex {
    print(line[i])
    i = line.index(after: i)
}
// prints -> 
// h
// e
// l
// l
// o
MathewS
  • 2,267
  • 2
  • 20
  • 31
  • This might be a stupid question but is it possible to find the index of a string like you did above, and convert that index back to a string or character? Maybe I'm looking at it wrong and don't see it but just thought I'd ask @MathewS – Laurence Wingo Jan 06 '17 at 23:07
  • 1
    Yes! There's a method index(of:) that finds index of a character: https://developer.apple.com/reference/swift/string.characterview I've never actually used it – MathewS Jan 06 '17 at 23:19
2

Working with Strings in Swift was changed several times during it's evolution and it doesn't look like C++ at all. You cannot subscript string to obtain individual characters, you should use index class for that. I recommend you read this article: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html

user3237732
  • 1,976
  • 2
  • 21
  • 28
1

As already pointed out in the other answers, the compiler error is caused by the problem that you cannot index a Swift String with integers.

Another problem in your code is that you have a nested loop which is probably not intended.

Actually I would try to avoid string indexing at all and only enumerate the characters, if possible. In your case, you can easily keep track of the preceding character in a separate variable:

var lastChar: Character = " " // Anything except ":"

for char in line.characters {
    if char == "(" {
        maxOpen += 1;
        if lastChar != ":" {
            minOpen += 1;
        }
    }

    // ...

    lastChar = char
}

Or, since you only need to know if the preceding character is a colon:

var lastIsColon = false

for char in string.characters {
    if char == "(" {
        maxOpen += 1;
        if !lastIsColon {
            minOpen += 1;
        }
    }

    // ...

    lastIsColon = char == ":"
}

Another possible approach is to iterate over the string and a shifted view of the string in parallel:

for (lastChar, char) in zip([" ".characters, line.characters].joined(), line.characters) {

    // ...

}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • +1 on enumerating directly. I've been working on an algorithm that jumps up and down ranges and I'm starting to see String.Index as a hammer – MathewS Jan 06 '17 at 23:21
0

As others have already explained, trying to index into Swift strings is a pain. As a minimal change to your code, I would recommend that you just create an array of the characters in your line up front:

let linechars = Array(line.characters)

And then anywhere you need to index into the line, use linechars:

This:

if i == 0 || line[i-1] != ":" {

becomes:

if i == 0 || linechars[i-1] != ":" {
vacawama
  • 150,663
  • 30
  • 266
  • 294