UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Question about managing Array/Dictionary indices

Forums > Swift

So, I've been working on the teleporter challenge from UIKit project 26. I've got it set up so that it reads 0-9 as teleporters squares, with the goal of each one going to the next numbered square (with 9 looping back around to 0). Thing is, the numbers aren't necessarily going to be read in order when scanning the map to build it, so simply using .append() risks getting things out of order.

If this were PHP, it'd be fairly straightforward:

$teleporter_array[$square_id] = $teleporter_point

I think I've found a way to do a similar approach in Swift, but when I'm looping through the array (technically an NSMutableDictionary in this case) it maintains the keys in the order inserted instead of order by Int, and apparently NSMutableArray cannot be sorted? I've tried pulling the keys and sorting those so I can loop through them, but it doesn't seem to understand that the keys are simple Ints, and I'm not sure how to be any more explicit. Since I can't loop through things in a sorted manner, results are ending up all over the place (e.g. the map I'm testing is coming back as 0, 9, 5, 1, 6, 2, 7, 3, 8, 4).

Here's the code I'm working with, at present

class TeleporterGroup: NSObject {
    var points: NSMutableDictionary = [:] // Store the raw information

    var destinations: NSMutableDictionary = [:] // Store the comuted destinations so we can call it easily

    func addTeleporter(_ id: Any, at position: CGPoint) {
        let id = "\(id)"

        switch id {
            case _ where ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].contains(id):
                points.addEntries(from: [Int(id): position])
            default:
                return
        }
    }

    func setDestinations() {
        var firstIndex = -1
        var finalIndex = -1
        var lastPoint = CGPoint()

        for (index, point) in points.reversed() {
            guard let point = point as? CGPoint else { continue }
            guard let index = index as? Int else { continue }

            if (firstIndex == -1) {
                lastPoint = point
                firstIndex = index
                finalIndex = index
            } else {
                destinations.addEntries(from: [index: lastPoint])
                print("Teleporter \(index) should be set to \(lastPoint.x),\(lastPoint.y)")
                lastPoint = point
                finalIndex = index
            }
        }

        destinations.addEntries(from: [finalIndex: finalIndex])
    }
}

Since we know they're Ints we could just add at index index - 1, however that creates issues if there are missing numbers for some reason, which is why I'm trying to make the sorting method work.

Any suggestions on how to fix it? Thanks in advance!

2      

I'm not sure why you would use NSMutableArray instead of just Array, which most definitely can be sorted. NSMutableArray can be sorted but it's more complex to do so than to sort a Swift Array.

And I'm not sure I understand why you are using NSMutableDictionary and then having to deal with casting all over the place instead of just using a Dictionary with the key type Int and value type CGPoint. So that would make the class properties like this instead:

var points = [Int: CGPoint]()
var destinations = [Int: CGPoint]()

Then you can pull the keys and sort them with:

let keys = [Int](points.keys).sorted()

This would also solve the issue of how in addTeleporter(_:at:) you pass in the first parameter as Any type, then shove it into a String for testing its value, then convert that String to an Int for inserting into points. You could just have it an Int to begin with then test if it's in the range 0...9 and you also wouldn't have to cast it to use it as a key for your dictionary.

3      

The reason I went with NSMutableDictionary instead of Array was that I needed to be able to assign to specific numbered indexes that could come out-of-sequence. From what I've seen with Array, trying to assign via subscript to an index that does not exist yet produces an error, and when looping through them it collapses to fill gaps in index numbers (hence why you're supposed to use .enumerated.reversed() when looping through them and deleting members, no?)

Admittedly, I'd tried the plain Dictionary route and was running into errors with typing; based on the examples it appears I had some misunderstandings how defining them worked (particularly in that you could define types for the keys—I'd always thought the keys were bare string literals). I'll give it a shot with that this evening and see how things work.

Thanks for the tips!

2      

Hello again! Reporting back to let you know that it all came together right away once I figured out how to type the elements of the dictionary when declaring it. I know it seems silly, but type safety has really been the single biggest issue I've had with learning Swift.

As you noted, getting the Dictionary set up properly also did away with the need for a lot of the excess typecasting that the system was demanding previously.

For the benefit of anyone else coming by later who has the same issue, here's the updated class conformed to the recommendations above.

class Teleporter: NSObject {
    var points: Dictionary = [Int: CGPoint]()
    var destinations: Dictionary = [Int: CGPoint]()

    func addTeleporter(_ id: String, at position: CGPoint) {
        guard let id = Int(id) else { return }
        points[id] = position
    }

    func setDestinations() {
        var firstIndex = -1 // We won't know what to assign to the first index called until everything else has run
        var finalPoint = CGPoint() // The final point will be the destination for firstIndex
        var lastPoint = CGPoint() // The last index's point is assigned to the current index

        let keys = [Int](points.keys).sorted()

        for (index) in keys.reversed() {
            if (firstIndex == -1) {
                lastPoint = points[index]!
                firstIndex = index
                finalPoint = points[index]!
            } else {
                destinations[index] = lastPoint
                lastPoint = points[index]!
                finalPoint = points[index]!
            }
        }

        destinations[firstIndex] = finalPoint
    }
}

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.