Reputation: 1189
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
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:
find()
on an array performs quite poorly as the list grows into the hundreds of cities O(N).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!
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
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
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
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
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