Append array data into Struct variable - swift

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
}

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 Doubles (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 Doubles, 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
}
The direct problem
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:
First attempt: using 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.
Second attempt: manual indexing
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]
}
Third attempt: skip the 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...
Fixing the flaws with 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!
Don't store doubles for lats and longs
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 Doubles. 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),
]
Mutability
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)
}
}
And Finally...
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)")
}

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]
}
}

Related

leaflet, generate continuous coordinates based on a Polyline

I don’t have detailed latitude and longitude data, I hope to automatically generate continuous latitude and longitude coordinates based on the two points.
On the one hand, there is the need for animation effects, such as driving the position of the icon according to the height of the mouse sliding.
For example, I have a polyline like this:
polylineData: [
{ lat: 36.181681, lon: 121.913284 },
{ lat: 23.804980, lon: 127.497168}]
Are there any plugin or calculation formulas that can help me generate continuous coordinates from point { lat: 36.181681, lon: 121.913284 } to point { lat: 23.804980, lon: 127.497168}
polylineData: [
{ lat: 36.181681, lon: 121.913284 },
{ lat: 36.171212, lon: 121.922323 },
.
.
.
{ lat: 23.121631, lon: 127.334284 },
{ lat: 23.804980, lon: 127.497168 }]
The Libraries Turf.js or GeometryUtil is very helpful.
You can calculate the latlng between two points and the distance
destinationOnSegment
or interpolate on the line: interpolateOnLine
Example:
var i = 0;
setInterval(function(){
var result = L.GeometryUtil.interpolateOnLine(map, poly, i); // between 0 and 1
marker.setLatLng(result.latLng)
i += 0.001;
},10);
https://jsfiddle.net/falkedesign/5gy8hmfu/

Swift iOS -How come I can loop through an array of class objects and make property changes but not structs [duplicate]

This question already has answers here:
Is Swift Pass By Value or Pass By Reference
(10 answers)
Swift can change struct declared with let if using an index but not if using a loop
(3 answers)
Closed 3 years ago.
If I loop through an array of Class objects I can make changes to a property on it
class Country {
var name: String?
var region: String?
init(name: String?, region: String?) {
self.name = name
self.region = region
}
}
let canada = Country(name: "Canada", region: "North America")
let mexico = Country(name: "Mexico", region: "North Ameria")
let france = Country(name: "France", region: "Europe")
let korea = Country(name: "Korea", region: "Asia")
var countryArr = [canada, mexico, france, korea]
// this works fine
let transformed = countryArr.map { $0.name = "Random" }
But if I try this with Struct objects I get
Cannot assign to property: '$0' is immutable
struct Country {
var name: String?
var region: String?
}
var canada = Country(name: "Canada", region: "North America")
var mexico = Country(name: "Mexico", region: "North Ameria")
var france = Country(name: "France", region: "Europe")
var korea = Country(name: "Korea", region: "Asia")
var countryArr = [canada, mexico, france, korea]
// this gets an error
let transformed = countryArr.map { $0.name = "Random" }
The issue is caused by the fact that structs are value types, so mutating any properties of the struct mutates the struct instance itself as well and the closure input arguments in map are immutable. So when you try to mutate a property $0 in the closure of map, you are trying to mutate $0 itself in case map is called on a collection of value types.
On the other hand, classes are reference types, so mutating a property of a class instance doesn't mutate the instance itself.
A solution for your problem is to create a mutable copy of the struct instance in the map, mutate its name property and return that. There are two solutions, if you have a small number of properties on your type, calling its memberwise initialiser is easier, but if you have a lot of properties and only want to mutate a few, copying the struct and then modifying the necessary properties is the better choice.
let transformed = countryArr.map { Country(name: "Random", region: $0.region) }
let transformed2 = countryArr.map { country->Country in
var copy = country
copy.name = "Random"
return copy
}

Swift struct referencing another struct

I'm testing the following code in an Xcode Playground.
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
}
var MyCoordinate: Coordinate = Coordinate.init(latitude: 30.1, longitude: 10.2)
var LandmarkA: Landmark = Landmark.init(name: "My Landmark", foundingYear: 2017, location: MyCoordinate)
var LandmarkB: Landmark = Landmark.init(name: "My Landmark 2", foundingYear: 2016, location: MyCoordinate)
LandmarkA.location.latitude = 12.1
print (LandmarkA.location.latitude) // 12.1
print (LandmarkB.location.latitude) // 30.1
Now if I convert this code to classes instead of structs.
class Coordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class Landmark {
var name: String
var foundingYear: Int
var location: Coordinate
init(name: String, foundingYear: Int, location: Coordinate) {
self.name = name
self.foundingYear = foundingYear
self.location = location
}
}
var MyCoordinate: Coordinate = Coordinate.init(latitude: 30.1, longitude: 10.2)
var LandmarkA: Landmark = Landmark.init(name: "My Landmark", foundingYear: 2017, location: MyCoordinate)
var LandmarkB: Landmark = Landmark.init(name: "My Landmark 2", foundingYear: 2016, location: MyCoordinate)
LandmarkA.location.latitude = 12.1
print (LandmarkA.location.latitude) // 12.1
print (LandmarkB.location.latitude) // 12.1
Is it possible to get structs to behave the same way as classes in terms of what location is equal to? If I change the coordinate either by doing LandmarkA.location.latitude or MyCoordinate.latitude it should set both LandmarkA and LandmarkB. This behavior works with classes but I can't use Codable in classes and there is less code needed to setup structs.
The last two print statements in the first example I would like to both be 12.1 and the same since it's both referencing the same object.
This is the only thing I can't figure out with structs. structs have everything else I need, Codeable, functions within the struct, minimal setup code, etc.
I would really prefer to use structs but if this isn't possible I will probably settle for using classes.
Any thoughts of how to achieve this?

How to get index from GMSMarker tapped?

How to detect which marker was pressed on a map. I have few markers on map and class Marker in markers array downloaded from API, that contains some data.
for example i have data like this
[States(name: "text1", long: 110.42400399999997,lat: -7.0343237999999992),
States(name: "text2", long: 110.42769829999997, lat: -7.0856947999999997),
States(name: "text3", long: 110.42922440000007, lat: -7.3250846999999997),
States(name: "text4", long: 117.11625830000003, lat: -0.50436380000000003),
States(name: "text5", long: 110.43093620000002, lat: -7.0730081999999994)]
if i tap on marker that contain data 1 (States(name: "text1", long: 110.42400399999997,lat: -7.0343237999999992))
How do i get index 0. and if i tap on marker that contain data 2, How do i get index 1?
So I suppose you added the markers by iterating through the array like this:
for state in states { // assuming states is the array you showed in the question
let marker = GMSMarker(position: CLLocationCoordinate2D(latitude: state.lat, longitude: state.long))
// configure the marker...
marker.map = mapView
}
The idea is that you add the markers to an array as well, just after you created it. Since you created the markers in the order of the data, each item in the array containing the markers corresponds to the data at the same index.
Let's declare the array of markers at class level:
var markers = [GMSMarker]()
Then in the for loop above, add the marker to markers:
markers.append(marker)
Now you can find out which datum is tapped just by:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
if let index = markers.index(of: marker) {
let tappedState = states[index]
}
}

How to create an variable property based on the other property in the class

class someClass
{
// This is a list of restaurant names
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "Thai Cafe"]
// This is an array to record the restaurants I have visited
var restaurantIsVisited:[Bool] = [Bool](count: 21, repeatedValue: false)
// This is a function to set the restaurants I have visited
func setTheVisitedRestaurants(somenumber:Int)
{
self.restaurantIsVisited[somenumber] = true
}
}
let thisVisit = someClass()
thisVisit.setTheVisitedRestaurants(1)
let somearray = thisVisit.restaurantIsVisited
Above code has no error in playground. But what if I don't know the number of the total restaurants is 21 (It could be hundreds and I don't want to count). I tried the following code to create an immutable computed property. But it won't work because later on the property will be changed by function setTheVisitedRestaurants and it will return an error.
// var restaurantIsVisited:[Bool]{
// return [Bool](count: restaurantNames.count, repeatedValue: false)
// }
In a word, the question is like how to create an variable property based on the other property in the class. I am a beginner and I really tried. Please help!
Declare restaurantIsVisited with the lazy keyword. This will insure that it isn't created until it is accessed the first time, and by that time you will be able to ask restaurantNames for its count:
class someClass
{
// This is a list of restaurant names
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "Thai Cafe"]
// This is an array to record the restaurants I have visited
lazy var restaurantIsVisited:[Bool] = [Bool](count: self.restaurantNames.count, repeatedValue: false)
// This is a function to set the restaurants I have visited
func setTheVisitedRestaurants(somenumber:Int)
{
self.restaurantIsVisited[somenumber] = true
}
}
let thisVisit = someClass()
thisVisit.setTheVisitedRestaurants(1)
let somearray = thisVisit.restaurantIsVisited
println(somearray.count) // "21"
Why not use a struct to represent a restaurant, for example:
struct Restaurant {
let name: String
var visited: Bool
init(name: String, visited: Bool = false) {
self.name = name
self.visited = visited
}
}
Now it's much easier to keep track of which restaurants have been visited and which ones haven't because you haven't got to worry about information on each restaurant being separated into two arrays.
struct SomeStruct {
var restaurants = [Restaurant(name: "Cafe Deadend"),
Restaurant(name: "Homei"),
Restaurant(name: "Cafe Loisl")]
}
Usage:
var thisVisit = SomeStruct()
thisVisit.restaurants[0].visited = true