Reputation: 3
I want to merge data of array into my struct variable
Below is the code
I want to add distance array values in rest -> distance
distance = ["12.44","45.32","56.1","54.22"]
merge this distance array into struct variable distance
var rest : [Restaurants] = []
var distance : [String] = []
struct Restaurants {
var name:String
var lat:Double
var long:Double
var distance:String?
}
let rest1 = Restaurants(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil)
let rest2 = Restaurants(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil)
let rest3 = Restaurants(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil)
let rest4 = Restaurants(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil)
let rest5 = Restaurants(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil)
let rest6 = Restaurants(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil)
rest.append(rest1)
rest.append(rest2)
rest.append(rest3)
rest.append(rest4)
rest.append(rest5)
rest.append(rest6)
for location in rest {
distance.append(findDistance(from: location.lat, long: location.long))
}
// I want to add distance array values in rest -> distance
func findDistance(from lat: Double, long: Double) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)
let destination = CLLocation(latitude: lat, longitude: long)
let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}
Upvotes: 0
Views: 227
Reputation: 63157
There's a lot of stuff going on here, so I'll walk you through the solution.
First of all, rest
is a poor variable name. When I read it first, I thought this was "the rest", like the remainder of something, and was looking for where the "main" data is. Keystrokes don't cost you $, you can afford to just type out restaurants
.
Secondly, you create an empty array, and manually append all these restaurants to it. Instead, you can just create an array from an array literal, that contains all the restaurants directly. That omits the need to have separate
To answer your direct question, you could use zip
to iterate the rest
and distance
together, but the issue is that the rest1
, rest2
, ... variables, which were doomed to fail. What happens when you copy/paste a bunch of lines, and forget to change one of them, accidentally writing rest.append(rest6); rest.append(rest6)
, forgetting rest7
?
Thirdly, your findDistance(from:long:)
function takes two Double
s (a latitude and a longitude), and uses them to construct a CLLocation
. But when you look where this function is used, you already have a CLLocation
, which you decompose into two Double
s, only for findDistance(from:long:)
to immediately reglue them back together into a CLLocation
. Instead, just make findDistance
take a CLLocation
directly.
Fourthly, the Restaurants
datatype is miss named. It's not a plurality of restaurants. It models a single restaurant. Name it accordingly: Restaurant
Applying these improvements (and also fixing a bunch of indenting along the way), we get:
struct Restaurant {
var name: String
var lat: Double
var long: Double
var distance: String?
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
var distances = [Double]()
for location in restaurants {
distance.append(findDistance(to: location))
}
func findDistance(to destination: CLLocation) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)
let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}
Here I'll illustrate the process I went through to address the direct question. However, don't use any of these. All these are shitty designs trying to work around underlying flaws in design. I show them in order to demonstrate what successive refinement looks like.
Now, to address the direct question:
zip(_:_:)
A first solution approach might be to use zip
to iterate both restaurants
and distances
in parallel, and then mutating each restaurant
from each `distance. Something like this:
for (restaurant, distance) in zip(restaurants, distances) {
restaurant.distance = distance
}
However, this won't work. Since Restaurant
is a value type, the restaurant
variable in the loop is a copy of that in the array. Setting its distance mutates the copy, but doesn't effect the original in the array.
We can work around this, albeit in a much less pretty way, by looping over the indices:
for i in restaurants.indices {
restaurants[i] = distances[i]
}
distances
array.The second attempt works, but if the only purpose of distances
is for us to fill it with a bunch of values, only to immediately use them and discard them, why do we both having the array at all?
for i in restaurants.indices {
restaurant.location = findDistance(to: location)
}
This is still not great, however. The Restaurant
data type suffers from two stage initialization, which is a code smell. First we initialize it with nil
location, then we set it to a real location. Why bother? Why don't we just set the location directly?
let distance = findDistance(to: location)
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: distance),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: distance),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: distance),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: distance),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: distance),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
But this is still not good design...
findDistance(to:)
and Restaurant.distance
What does findDistance(to:)
even really do? Internally, it gets a distance from some hard-coded, unnamed location CLLocation(latitude: 31.461512, longitude: 74.272024)
. When I say someRestaurant.distance
, I would expect a distance to me. If it were a distance from some reference point A, I would expect the API to instead be spelt something like someResaurant.distanceFromNorthPole
, or something to that effect.
Furthermore, why is it the Restaurant
's job to store its distance to something else? What if I want restaurant.distanceToSouthPole
, restaurant.distanceToEquator
? The API would get pretty bloated, and a restaurant
type ends up doing way too much. And what if I restaurant.distanceToMe
? I can move, how is a pre-computed, stored value going to keep up with me as I move?
The solution is not to store a distance at all. Instead, provide an API that users of this data type can use to calculate distances to any point of their own choosing.
struct Restaurant {
var name: String
var lat: Double
var long: Double
func distance(from other: CLLocation) -> Double {
let selfLocation = CLLocation(latitude: self.lat, longitude: self.long)
return selfLocation.distance(from: other)
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA:
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}
And surprise, this is still not the best we can make this!
That's what CLLocation
is for. Notice that almost all uses of lat
and long
require first boxing it into a CLLocation
. So let's just store that directly, rather than separating individual components and passing them around independently. This prevents bugs like useLocation(lat: self.lat, long: self.long /* oops! */)
.
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
However, if you do this, the initializer now requires a CLLocation
instead of two separate lat
/long
Double
s. This is better for interacting with location APIs (where CLLocation
is the "common currency" type for exchanging location info), but it's more cumbersome for hard-coded locations like your restaurants, because all of your initializer calls get bloated with a bunch of calls to CLLocation.init(latitude:longitude:)
:
let restaurants = [
Restaurant(name: "English Tea House", CLLocation(latitude: 31.461812, longitude: 74.272524)),
Restaurant(name: "Cafe Barbera", CLLocation(latitude: 31.474536, longitude: 74.268103)),
Restaurant(name: "Butler's Chocolate", CLLocation(latitude: 31.467505), longitude: 74.251908)),
Restaurant(name: "Freddy's Cafe", CLLocation(latitude: 31.461312, longitude: 74.272124)),
Restaurant(name: "Arcadian Cafe", CLLocation(latitude: 31.464536, longitude: 74.268603)),
Restaurant(name: "Big Moes", CLLocation(latitude: 31.467305, longitude: 74.256908)),
]
To remedy this, we can tuck the CLLocation.init(latitude:longitude:)
away into a little initializer for convenience. I do so in an extension of Restaurant
rather than directly in the initial declaration of Restaurant
, because doing so preserves the compiler-generated initializer (called the "member-wise initializer"), which would otherwise be replaced:
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
Which allows us to regain the previous nice syntax:
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
Restaurant names and locations are unlikely to change for the lifetime of an instance of your app, so there's no need to keep them mutable. So lets fix that:
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
We've come to the final stage. A well named Restaurant
that doesn't suffer from the need for two-stage initialization, that provides up-to-date distance data for any points a user might like, and that isn't vulnerable to copy paste errors thanks to lat
and long
being glued together into a CLLocation
.
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA you can do this (for example):
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}
Upvotes: 1
Reputation: 439
If I assume your question correct, you want to fill in the distance
property of each of the restaurants in rest
with your distance
variable. Also, assuming count of distance
variable & rest
variable are equal, you can do something like this,
if rest.count == distance.count {
(0..<rest.count).forEach {
rest[$0].distance = distance[$0]
}
}
Upvotes: 0