Search code examples
iosswiftfor-in-loop

"For-in loop requires '[String]?' to conform to 'Sequence'; did you mean to unwrap optional?" but I don't think I am using optionals


Here is what I'm trying to do but simplified down:

var adictionary = [String:[String]]()
adictionary["A"] = ["B", "C"]
adictionary["B"] = ["A", "C"]
adictionary["C"] = ["A"]
adictionary["D"] = []

var newdic = Array(adictionary.keys).reduce(into: [String: Bool]()) { $0[$1] = false } //I make an arr from a dictionary's keys of type [String:[String]]

for (key, val) in newdic{
     for arr in adictionary[key]{
          if !(adictionary[arr].contains(key)){
               newdic[key] = true
          }
     }
}

I get these errors, when I run the above simplified version of the code I am trying to run in an ios app:

main.swift:12:28: error: value of optional type '[String]?' must be unwrapped to a value of type '[String]' for arr in adictionary[key]{

main.swift:12:28: note: coalesce using '??' to provide a default when the optional value contains 'nil' for arr in adictionary[key]{

main.swift:12:28: note: force-unwrap using '!' to abort execution if the optional value contains 'nil' for arr in adictionary[key]{

I don't understand, what is wrong with what I am doing? What do these errors mean? It seems like Swift thinks I have an optional somewhere? But I don't see how I do... Any help is much appreciated.

If I change this line: for arr in adictionary[key]{

To: for arr in adictionary[key]!{

It fixes the issues and new errors appear on the if !(a... line. I still don't understand why that fixed it.


Solution

  • Swift dictionary accesses always return optional values, because the lookup will return nil if the key doesn't exist. It is up to you to handle this resulting optional is a safe way. Adding ! is rarely the right way to fix it because your code will crash if nil is returned.

    The first time you get an optional is here:

    for arr in adictionary[key] {
    

    Again, Swift doesn't know if key exists in adictionary so it returns an optional. A safe way to fix this is to use the dictionary lookup which returns a default value when the key doesn't exist. In this case, returning an empty array seems like a good choice since your for loop will then just do nothing:

    for arr in adictionary[key, default:[]] {
    

    Next, you get an optional here:

    if !(adictionary[arr].contains(key)){
    

    Again, you need to decide how to safely handle the fact that dictionary[arr] could return nil. The same trick works here: return an empty array if arr doesn't exist:

    if !(adictionary[arr, default:[]].contains(key)){
    

    Here is the final version of your code:

    var adictionary = [String:[String]]()
    adictionary["A"] = ["B", "C"]
    adictionary["B"] = ["A", "C"]
    adictionary["C"] = ["A"]
    adictionary["D"] = []
    
    var newdic = Array(adictionary.keys).reduce(into: [String: Bool]()) { $0[$1] = false } //I make an arr from a dictionary's keys of type [String:[String]]
    print(newdic)
    
    for key in newdic.keys {
        for arr in adictionary[key, default:[]] {
            if !(adictionary[arr, default:[]].contains(key)){
                   newdic[key] = true
              }
         }
    }