Home > Software design >  How can I define insert function for Set when Set is optional in Swift?
How can I define insert function for Set when Set is optional in Swift?

Time:07-31

My goal is to be able insert new item to an optional set which this set has nil value before. For that reason i created this extension, but it does not work, and still I cannot insert a new item to a nil set.

extension Optional where Wrapped == Set<String> {
    func myInsert(_ value: String) -> Self {
        if let unwrappedSet: Set<String> = self {
            var newSet: Set<String> = unwrappedSet
            newSet.insert(value)
            return newSet
        }
        else {
            let set: Set<String>? = [value]
            return set
        }
    }
}

use case:

func myTest() -> Set<String>? {
    var set: Set<String>? = nil
    set.myInsert("Hello")
    return set
}

if let set: Set<String> = myTest() {
    print(set)
}

I refuse to believe there is issue with my extension, and i think the issue is from xcode itself, look the function below it is same function but outside of extension, it does works!

func myInsert(set: Set<String>?, value: String) -> Set<String>? {
    if let unwrappedSet: Set<String> = set {
        var newSet: Set<String> = unwrappedSet
        newSet.insert(value)
        return newSet
    }
    else {
        let set: Set<String>? = [value]
        return set
    }
}

use case:

let set: Set<String>? = nil
let newSet = myInsert(set: set, value: "Hello")
let newSet2 = myInsert(set: newSet, value: "World")
print(newSet2)

result:

Optional(Set(["Hello", "World"]))

CodePudding user response:

From your test case code:

var set: Set<String>? = nil
set.myInsert("Hello")

you expect the variable set to change from nil to a wrapped Set<String> containing the String "Hello". In order for the value to modify itself, the func must be mutating.

Make myInsert a mutating func and assign the new set to self. Since myInsert is mutating, it doesn't need to return a value which your test is ignoring anyway:

extension Optional where Wrapped == Set<String> {
    mutating func myInsert(_ value: String) {
        if self == nil {
            self = [value]
        } else {
            self?.insert(value)
        }
    }
}

Test

func myTest() -> Set<String>? {
    var set: Set<String>? = nil
    set.myInsert("Hello")
    set.myInsert("Goodbye")
    return set
}

if let set = myTest() {
    print(set)
}
["Hello", "Goodbye"]

Making insert more usable

(Thanks to @LeoDabus for his suggestions)

We can make this work with a Set of any type and make it more like the original insert on Set by having it return a @discardableResult containing a tuple with a Bool indicating if a the value was inserted and the memberAfterInsert:

extension Optional where Wrapped: SetAlgebra {
    @discardableResult
    mutating func insert(_ newMember: Wrapped.Element) -> (inserted: Bool, memberAfterInsert: Wrapped.Element) {
        if self == nil {
            self = .init()
        }
        return self!.insert(newMember)
    }
}

CodePudding user response:

vacawama and Leo's answer is correct but you want to understand why your code doesn't work. I'll try to explain why does not work as you expected. The function you created "myInsert" defines a Set object, inserts the parameter value and returns the newly created Set object. The problem is you are not actually assigning the created Set object to actual Set variable (which is "self") that you are working on.

if let unwrappedSet: Set<String> = self {
    var newSet: Set<String> = unwrappedSet // assigning nil valued "self" to a variable doesn't assign the original variable. Assigning nil to another variable makes that variable again nil. Both are equal (they are nil) but not same variables.
    newSet.insert(value) // inserts value to second Set object not to "self". So still, your actual Set doesn't contains the given value.
    return newSet // returns the value inserted Set object, not "self".
}
else {
    let set: Set<String>? = [value]
    return set
}

If we look at myTest() function you can see that your adding operation doesn't work on actual variable.

var set: Set<String>? = nil
set.myInsert("Hello") // at this point, myInsert function returns a new Set object with inserted given value. But this operation does not effect the original variable above. That means set variable is still nil
return set // returns nil variable.

I hope this helps.

  • Related