Kevin
Kevin

Reputation: 1189

How to Initialize An Array of the Information and Append

I am looking to be able to initialize an empty array structure that has the ability to .append() and can hold information similar to:

var locations = [
    ["title": "New York, NY",    "latitude": 40.713054, "longitude": -74.007228],
    ["title": "Los Angeles, CA", "latitude": 34.052238, "longitude": -118.243344],
    ["title": "Chicago, IL",     "latitude": 41.883229, "longitude": -87.632398]
]

Is this data type of an array of an array of dictionaries? I am looking to be able to access title , latitude, and longitude by location and I have never worked with something like this, so I am not sure how to initialize and .append()

Upvotes: 0

Views: 350

Answers (5)

Adam Kaplan
Adam Kaplan

Reputation: 1962

Here is a Dictionary version of the answer from @vacawama. It is an alternative that might suit certain use cases better (or worse).

There are a few notable improvements in this version:

  1. Looking up a key in a dictionary is very fast O(1), where as using find() on an array performs quite poorly as the list grows into the hundreds of cities O(N).
  2. Strings are allocated on the heap. This means the coordinate struct as designed will behave similar to a class in terms of performance (slower). Your question did not present a strong case for storing the location name in the struct.

First create a struct to represent a coordinate.

struct Coordinate {
    /// Latitude in decimal notation
    let latitude: Double

    /// Longitude in decimal notation
    let longitude: Double
}

Then create the empty collection (per your question)

/// Empty mapping of location string to latitude-longitude
var coordinates = [String: Coordinate]()

Here’s an example of one way to populate the dictionary

/// Populate the locations
coordinates["New York, NY"] = Coordinate(latitude: 40.713054, longitude: -74.007228)
coordinates["Los Angeles, CA"] = Coordinate(latitude: 40.713054, longitude: -74.007228)
coordinates["Chicago, IL"] = Coordinate(latitude: 40.713054, longitude: -74.007228)

Example output:

print(coordinates["New York, NY"] ?? "Unknown Location”)
> "Coordinates(latitude: 40.713054, longitude: -74.007227999999998)”

That’s it!


A little further...

If the locations are known in advance, and if there are not hundreds of them, you might try using a string-backed enum

The above should answer the original question, however Swift allows even more interesting approaches using the type system.

We have guaranteed that a Coordinate is a pair of Double numbers, great! But, there are no guarantees about the integrity of a location. Later on you might accidentally type coordinates[“New york, NY”]. This will fail because “york” is lowercase! (and also in ever other answer currently posted). Here’s the enum:

enum Location: String {
    case NY_NewYork = "New York, NY"
    case CA_LosAngeles = "Los Angeles, CA"
    case IL_Chicago = "Chicago, IL"
}

And change our dictionary key and usage accordingly

/// Empty mapping of location string to latitude-longitude
var coordinates = [Location: Coordinate]()

/// Populate the locations
coordinates[.NY_NewYork] = Coordinate(latitude: 40.713054, longitude: -74.007228)
coordinates[.CA_LosAngeles] = Coordinate(latitude: 40.713054, longitude: -74.007228)
coordinates[.IL_Chicago] = Coordinate(latitude: 40.713054, longitude: -74.007228)

We still have the original “New York, NY” title, but it is represented statically as the value Location.NY_NewYork. This means that the compiler will catch any mistakes that you might make!

One more thing: now that the location is a static constant value, we can actually put it back inside the struct without incurring a dreaded heap allocation! (The struct value will be a reference to the enumerated value).

Here’s the final version:

enum Location: String {
    case NY_NewYork = "New York, NY"
    case CA_LosAngeles = "Los Angeles, CA"
    case IL_Chicago = "Chicago, IL"
}

struct Coordinate {
    /// The logical name of the location referenced by this coordinate
    let location: Location

    /// Latitude in decimal notation
    let latitude: Double

    /// Longitude in decimal notation
    let longitude: Double
}

/// Empty mapping of location string to latitude-longitude
var coordinates = [Location: Coordinate]()

/// Populate the locations
coordinates[.NY_NewYork] = Coordinate(location: .NY_NewYork, latitude: 40.713054, longitude: -74.007228)
coordinates[.CA_LosAngeles] = Coordinate(location: .CA_LosAngeles, latitude: 40.713054, longitude: -74.007228)
coordinates[.IL_Chicago] = Coordinate(location: .IL_Chicago, latitude: 40.713054, longitude: -74.007228)
// or if initializing from a data source, something like...
// if let loc = Location(rawValue: "Chicago, IL") {
//     coordinates[loc] = Coordinate(location: loc, latitude: 40.713054, longitude: -74.007228)
// }

And the output

print(coordinates[Location.NY_NewYork] ?? "uknown”)
> "Coordinate(location: Location.NY_NewYork, latitude: 40.713054, longitude: -74.007227999999998)”

Cool! Now we have perfect type-safety, the convenience of keeping the location title in there, and a very high-performance architecture.

This is what makes Swift a special tool for iOS.

Upvotes: 1

vacawama
vacawama

Reputation: 154641

Your structure is an array of dictionaries: [[String : Any]].

As @Hamish suggested in the comments, you'd be better served to store your data as an array of structs.

Here is one possible implementation:

struct Location: CustomStringConvertible {
    var title: String
    var latitude: Double
    var longitude: Double

    var description: String { return "(title: \(title), latitude: \(latitude), longitude: \(longitude))"}
}

var locations = [
    Location(title: "New York, NY", latitude: 40.713054, longitude: -74.007228),
    Location(title: "Los Angeles, CA", latitude: 34.052238, longitude: -118.243344),
    Location(title: "Chicago, IL", latitude: 41.883229, longitude: -87.632398)
]

// Append a new location...    
locations.append(Location(title: "Boston, MA", latitude: 42.3601, longitude: -71.0589))

print(locations)
[(title: New York, NY, latitude: 40.713054, longitude: -74.007228),
(title: Los Angeles, CA, latitude: 34.052238, longitude: -118.243344),
(title: Chicago, IL, latitude: 41.883229, longitude: -87.632398),
(title: Boston, MA, latitude: 42.3601, longitude: -71.0589)]
func lookUp(title: String, in locations: [Location]) -> [Location] {
    return locations.filter { $0.title.range(of: title) != nil }
}

print(lookUp(title: "CA", in: locations))
[(title: Los Angeles, CA, latitude: 34.052238, longitude: -118.243344)]
print(lookUp(title: "Chicago", in: locations))
[(title: Chicago, IL, latitude: 41.883229, longitude: -87.632398)]

Upvotes: 1

user7014451
user7014451

Reputation:

It may be best to use just a Dictionary with a tuple. That way you can insure you have unique keys.

var myCoordinates:(Float,Float)?
var myCities = [String:(Float,Float)]()

The key (String type) is the city name and the tuple (the two float types) are your longitude/latitiude values.

myCities["New York, NY"] = (40.713054,-74.007228)
myCoordinates = myCities["New York, NY"]?
print(String(describing: myCoordinates?.0))
print(String(describing: myCoordinates?.1))
myCoordinates = myCities["Los Angeles, CA"]
print(String(describing: myCoordinates?.0))
print(String(describing: myCoordinates?.1))

This code yields the following console output:

Optional(40.7130547)
Optional(-74.007225)
nil
nil

Upvotes: 0

MathewS
MathewS

Reputation: 2307

Yes, it's an array of dictionaries, but the dictionary values are either type String or Double, so to accommodate both of these values, the dictionary type will be <String: Any>.

If you were explicitly decalairing the type for the locations variable (instead of being inferred) it would be:

var locations: [Dictionary<String, Any>] = ...

To access the values in the array you can use index subscripting where you pass in an Int for the index of the dictionary at that position:

let firstLocation = locations[0]
// firstLocation: [String: Any]
// firstLocation -> [ "title": "New York, NY", "latitude": 40.713054, "longitude": -74.007228]

You can dig in deeper with a second subscript accessing the value for the dictionary key:

let firstLocationTitle = locations[0]["title"]
// firstLocationTitle: Any?
// firstLocationTitle -> New York, NY

let message = "I'm in" + firstLocationTitle
// fails because operator '+' cannot be applied to operands of type 'String' and 'Any?'

To get access to the underlying String type, you need to cast from Any? to String:

let firstLocationTitle = locations[0]["title"] as! String
// firstLocationTitle: String
// firstLocationTitle -> New York, NY

let message = "I'm in" + firstLocationTitle
// message -> "I'm in New York, NY"

To append a new location, it just needs to be the same type, here's how you could add a Boston dictionary to locations:

let boston: [String: Any] = ["title": "Boston, MA", "latitude": 39.713054, "longitude": -88.632398]
locations.append(boston)

Upvotes: 1

christian
christian

Reputation: 495

var locations = [
    ["title": "New York, NY",    "latitude": 40.713054, "longitude": -74.007228],
    ["title": "Los Angeles, CA", "latitude": 34.052238, "longitude": -118.243344],
    ["title": "Chicago, IL",     "latitude": 41.883229, "longitude": -87.632398]
]
for(loc) in locations {
    if let title = loc["title"] as? String, let lat = loc["latitude"] as? Double, let lon = loc["longitude"] as? Double {
        print("title: \(title), latitude:\(lat), longitude:\(lon)")
    }
}

Upvotes: 0

Related Questions