Need Help for Bronze Challenge

Currently, I am using a for-in loop to iterate through the array of Assets and want to remove the asset that matches by name matching by setting it to nil, but I get a compiler error saying “‘nil’ cannot be assigned to type ‘Asset’”. Is there a better way to implement the remove method without having to iterate through the array?

Below is my current implementation which is currently wrong.

    func remove(_ name: String) {
        for asset in assets {
            if asset.name == name {
                asset = nil
            }
        }
    }

Even if the compiler did not issue that error, setting the asset variable to nil won’t still cause the asset to be removed from the container because doing so would only decrement the reference count by 1.

You have to first find the matching item in the container, then tell the container itself to remove it.

By that are you saying for the Vault class and the Asset class to both have a remove function? Meaning the remove function in the Vault class will find the matching item and call the remove function in the Asset class to remove the asset?

I don’t have the book. However, see Array for more information, especially the following functions.

func remove(at: Int) -> Element
// Removes and returns the element at the specified position.

func removeAll(where: (Element) -> Bool)
// Removes all the elements that satisfy the given predicate.

Then you should be able to figure out how to implement the remove function.

The downside to removeAll is that it doesn’t tell you which asset objects are removed. Since you need to make changes to the removed assets (to undo the changes made in store) that might not work out (depending on how you implement things).

As for where to put the remove function, you could do it either way. Since the asset knows what vault it’s in, you could tell the asset to remove itself from its container. Or you could tell the vault to remove a specific asset. My personal preference would be for the latter. Either way I would try to keep the removal code all in one place, I’d prefer not to have removal code in both Vault and Asset. But there are a few error conditions the code currently doesn’t handle (adding the same asset to a vault multiple times, adding the same asset to more than one vault) that I haven’t completely thought through - it’s possible that having removal code in both classes would make the most sense depending on how those play out. This is one of those Bronze challenges that’s a bit like tugging on a loose thread.

1 Like

@JonAult, I took your advice on having just one removal function and kept it in the Vault class, which seems to be working.

Here’s what the function looks like:

    // Bronze Challenge
    func remove(_ name: String) {
        for (i, asset) in assets.enumerated() {
            if asset.name == name {
                assets.remove(at: i)
                asset.container = nil
            }
        }
    }

Testing the remove function which will remove the asset from the array and also remove its reference to the vault.

class Simulation {
    func run() {
        let vault13 = Vault(number: 13)
        print("Created \(vault13)")
        
        let coin: Asset = Asset(name: "Rare Coin", value: 1_000.0)
        let gem: Asset = Asset(name: "Big Diamond", value: 5_000.0)
        let poem: Asset = Asset(name: "Magnum Opus", value: 0.0)
        
        vault13.store(coin)
        vault13.store(gem)
        
        print("Created some assets: \([coin, gem, poem])")
        
        print("vault13 before: \(vault13.assets)")  // vault13 before: [Asset(Rare Coin, worth 1000.0, in Vault(13)), Asset(Big Diamond, worth 5000.0, in Vault(13))]
        vault13.remove(gem.name)
        print("vault13 after: \(vault13.assets)")  // vault13 after: [Asset(Rare Coin, worth 1000.0, in Vault(13))]
        print("gem: \(gem)")  // gem: Asset(Big Diamond, worth 5000.0, not stored anywhere)
    }
}

let simulation = Simulation()
simulation.run()

dispatchMain()

You forgot something in your remove function - take another look at what store does to the asset.

@JonAult, Is it to set the value of the asset being removed to 0? This is so that the vault will update and print the new change to the console.

    // Bronze Challenge
    func remove(_ name: String) {
        for (i, asset) in assets.enumerated() {
            if asset.name == name {
                asset.value = 0
                assets.remove(at: i)
                asset.container = nil
            }
        }
    }

No, that’s not what I was getting at. Removing an asset from a vault shouldn’t change the value of the asset.

Try changing the value of gem after you remove it from vault13. Look at what gets printed and ask yourself if it makes sense.

@JonAult, I see. An asset that is removed should not be able to update the total vault value. So an Asset’s changeHandler should be reinitialized during removal.

    // Bronze Challenge
    func remove(_ name: String) {
        for (i, asset) in assets.enumerated() {
            if asset.name == name {
                asset.changeHandler = {_ in}
                assets.remove(at: i)
                asset.container = nil
            }
        }
    }

:+1:

One final nitpick, when you find a match & remove it from the vault you should break out of the for loop. If you find a second match it won’t be deleted correctly because it’s no longer in its original position in the array (and if it’s at the end of the array the program will crash). If you want to handle multiple deletions, you’ll need to use a different method of locating them.

@JonAult Thank you for your help in walking me through this. It definitely helped strengthen my understanding of the chapter as a whole!

Remove asset.changeHandler = {_ in}
assets.remove(at: i)
asset.container = nil

lines.

@StevenTillson02 Why would I remove those three lines?