Index out of range error in my function using str.split(separator: ch).map { String($0) }

Still requires an error trap to prevent an index out of range when eg. hello.world. (no word after the world full-stop, hello..today also produces the same error for example) is mistakenly entered in my firstCardWords field. I have managed to trap spaces, not having the required number of full-stops and a nil entry but nothing after a full-stop produces an error that crashes the app. Also is there a better way to handle this input (preferably not too complicated ) to prevent all instances that will cause index out of range in this example?

func makeFirstCardWordLetters () {
     if cards.firstCardWords == "" {
       cards.firstCardWords = "FIRST.CARD.WORD"
     }
     var numOccurrences = cards.firstCardWords.filter{ $0 == "." }.count
     if numOccurrences != 2 {
       cards.firstCardWords = "TWO.FULL-STOPS.PLEASE"
     }
     numOccurrences = cards.firstCardWords.filter{ $0 == " " }.count
     if numOccurrences != 0 {
       cards.firstCardWords = "NO.SPACES.PLEASE"
     }
     let str = cards.firstCardWords
     let ch = Character(".")
     cards.firstCardLettersResult = str.split(separator: ch).map { String($0) }
     cards.firstCardFirstLetters = cards.firstCardLettersResult[0] as! String
     cards.firstCardSecondLetters = cards.firstCardLettersResult[1] as! String
     cards.firstCardThirdLetters = cards.firstCardLettersResult[2] as! String
     makeSecondCardWordLetters()
   }
Answered by Claude31 in 741670022

I missed this case.

It is because of how split works.

If nothing before separtor, then no item is created.

For instance

"hello.you.boy" creates ["hello", "you", "boy"]

but

".you.boy" creates [ "you", "boy"]  // only 2 items

and

".you." creates ["you"]

So you could make an initial test:

func makeFirstCardWordLetters () {

    let test = firstCardWords.split(separator: ".").map { String($0) }
    if test.count < 3 {
        firstCardWords = "MUST-HAVE.WORDS.BETWEEN-FULL-STOPS"
        return
    }

To get the detailed diagnostic of what is missing:

extension String {  // Credit https://stackoverflow.com/questions/40413218/swift-find-all-occurrences-of-a-substring
    func indices(of string: String) -> [Int] {
        return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
    }
}

func makeFirstCardWordLetters () {

    let positions = firstCardWords.indices(of: ".")

    if positions.count < 2 {
        firstCardWords = "MUST-HAVE.WORDS.BETWEEN-FULL-STOPS"
        return
    }
    if positions[0] == 0 {
        firstCardWords = "FIRST.WORD.MISSING"
        return
    }
    if positions[1] == positions[0] + 1 {
        firstCardWords = "SECOND.WORD.MISSING"
        return
    }
    if positions[1] == firstCardWords.count - 1 {
        firstCardWords = "THIRD.WORD.MISSING"
        return
    }

    // more code

When you split, if you have not 2 full stops chars, then split array has not 3 elements. In addition if you have not 2 full stops and have a space, what will be firstCardWords ?

So change as follows:

func makeFirstCardWordLetters () {
     if cards.firstCardWords == "" {
       cards.firstCardWords = "FIRST.CARD.WORD"
       return // <<-- ADD THIS
     }
     var numOccurrences = cards.firstCardWords.filter{ $0 == "." }.count
     if numOccurrences != 2 {
       cards.firstCardWords = "TWO.FULL-STOPS.PLEASE"
       return // <<-- ADD THIS ; now on, we are sure to have 2 full stops
     }
     numOccurrences = cards.firstCardWords.filter{ $0 == " " }.count
     if numOccurrences != 0 {
       cards.firstCardWords = "NO.SPACES.PLEASE"
       return // <<-- ADD THIS ; now on, we are sure to have no space
     }

    // As we have 2 full stops, we are sure to have 3 items in str.split
     let str = cards.firstCardWords
     let ch = Character(".")
     cards.firstCardLettersResult = str.split(separator: ch).map { String($0) }
     cards.firstCardFirstLetters = cards.firstCardLettersResult[0] as! String
     cards.firstCardSecondLetters = cards.firstCardLettersResult[1] as! String
     cards.firstCardThirdLetters = cards.firstCardLettersResult[2] as! String
     makeSecondCardWordLetters()
   }

So sorry to say but I am still getting the error.

In this example what is return supposed to do?

I understand it is hard to diagnose something when you only get a snippet and for that I am sorry.

In my app the code doesn’t return to the Form to make any corrections until all the functions have run and the app crashes if for instance hello.world. or hello..today is entered instead of hello.world.today ie. two elements instead of three.

Maybe I need to look at trapping errors at the input stage. Having said that it is not something I know how to do at this stage (more to learn).

Thank you again.

Regards

Guy

To better explain what I am trying to achieve…

I am wanting to trap this error eg. (hello.world.) before the cards.firstCardLettersResult = str.split(separator: ch).map { String($0) } line of code so not to crash the app.

My other traps do this for the other types of input error by making a message that is the correct format (contain three elements) for cards.firstCardLettersResult = str.split(separator: ch).map { String($0) } to handle.

Without a crash my code then shows another View with all the inputs and if any of the inputs are incorrect (typos) or contain any of the trap messages my code has a button in this View to return to the form so inputs can be edited. This method is not perfect because it overwrites the original input in the form with the error message so the error message has to be deleted and word has to be typed again.

I have just had a brainwave, I just need to work out how to code it!

The logic is as follows…

if the character before the first full-stop is nil then create an error message eg FIRST.WORD.MISSING If the character before the second full-stop (optional: and after the first full-stop) is nil then create an error message eg SECOND.WORD.MISSING If the character after the second full-stop is nil create an error message eg THIRD.WORD.MISSING

I may need help with this!

I think I have gone down the wrong path because although this works I haven’t been able to expand on it with suffix etc. (after the first full-stop (second word)) and it doesn’t address after the second full-stop (third word).

Back to the drawing board!

let threewords = cards.firstCardWords
var firstWord = ""
        if let index = threewords.firstIndex(of: ".") {
            firstWord = String(threewords.prefix(upTo: index))
        }
        if firstWord == "" {
            cards.firstCardWords = "FIRST.WORD.MISSING"
        }
Accepted Answer

I missed this case.

It is because of how split works.

If nothing before separtor, then no item is created.

For instance

"hello.you.boy" creates ["hello", "you", "boy"]

but

".you.boy" creates [ "you", "boy"]  // only 2 items

and

".you." creates ["you"]

So you could make an initial test:

func makeFirstCardWordLetters () {

    let test = firstCardWords.split(separator: ".").map { String($0) }
    if test.count < 3 {
        firstCardWords = "MUST-HAVE.WORDS.BETWEEN-FULL-STOPS"
        return
    }

To get the detailed diagnostic of what is missing:

extension String {  // Credit https://stackoverflow.com/questions/40413218/swift-find-all-occurrences-of-a-substring
    func indices(of string: String) -> [Int] {
        return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
    }
}

func makeFirstCardWordLetters () {

    let positions = firstCardWords.indices(of: ".")

    if positions.count < 2 {
        firstCardWords = "MUST-HAVE.WORDS.BETWEEN-FULL-STOPS"
        return
    }
    if positions[0] == 0 {
        firstCardWords = "FIRST.WORD.MISSING"
        return
    }
    if positions[1] == positions[0] + 1 {
        firstCardWords = "SECOND.WORD.MISSING"
        return
    }
    if positions[1] == firstCardWords.count - 1 {
        firstCardWords = "THIRD.WORD.MISSING"
        return
    }

    // more code

Thank you so much!

At the moment I will use let test = firstCardWords.split(separator: ".").map { String($0) } in my App because I understand it.

Until I understand how the extension works I won’t use it.

I want to understand every aspect of my App so that despite receiving some help in building it and without that help (from you, Claude31) I can feel comfortable I built it.

Thanks again

Guy

Index out of range error in my function using str.split(separator: ch).map { String($0) }
 
 
Q