Здесь много чего происходит, поэтому я проведу вас через решение.
Прежде всего, rest
- плохое имя переменной.Когда я прочитал это первым, я подумал, что это «остальное», как и остаток чего-то, и искал, где находятся «основные» данные.Нажатие клавиш не стоит вам $, вы можете просто набрать restaurants
.
Во-вторых, вы создаете пустой массив и вручную добавляете в него все эти рестораны.Вместо этого вы можете просто создать массив из литерала массива, который содержит все рестораны напрямую.Это исключает необходимость иметь отдельные
. Чтобы ответить на ваш прямой вопрос, вы можете использовать zip
для повторения rest
и distance
вместе, но проблема в том, что rest1
, rest2
... переменные, которые были обречены на провал.Что происходит, когда вы копируете / вставляете несколько строк и забываете поменять одну из них, случайно написав rest.append(rest6); rest.append(rest6)
, забыв rest7
?
В-третьих, ваша findDistance(from:long:)
функция занимает две Double
с(широта и долгота) и использует их для построения CLLocation
.Но когда вы посмотрите, где используется эта функция, у вас уже есть CLLocation
, который вы разбиваете на два Double
s, только на findDistance(from:long:)
, чтобы немедленно объединить их обратно в CLLocation
.Вместо этого просто заставьте findDistance
принять CLLocation
напрямую.
В-четвертых, тип данных Restaurants
имеет имя miss.Это не множество ресторанов.Это модели одного ресторана.Назовите его соответствующим образом: Restaurant
Применяя эти улучшения (а также исправляя кучу отступов по пути), мы получим:
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
}
Прямая проблема
Здесь я проиллюстрирую процесс, который я прошел, чтобы ответить на прямой вопрос.Однако не используют ни одного из них. Все это дерьмовые дизайны, пытающиеся обойти основные недостатки в дизайне.Я показываю их, чтобы продемонстрировать, как выглядит последовательное уточнение.
Теперь, чтобы ответить на прямой вопрос:
Первая попытка: использование zip(_:_:)
Первый подход к решениювозможно, следует использовать zip
для итерации одновременно restaurants
и distances
, а затем изменять каждый restaurant
с каждого `расстояния.Примерно так:
for (restaurant, distance) in zip(restaurants, distances) {
restaurant.distance = distance
}
Однако это не сработает.Поскольку Restaurant
является типом значения, переменная restaurant
в цикле является его копией в массиве.Установка его расстояния приводит к изменению копии, но не влияет на оригинал в массиве.
Вторая попытка: ручное индексирование
Мы можем обойти это, хотя и гораздо менее привлекательным способом,цикл по индексам:
for i in restaurants.indices {
restaurants[i] = distances[i]
}
Третья попытка: пропустить массив distances
.
Вторая попытка работает, но если единственная цель distances
- заполнить еес кучей значений, только для того, чтобы немедленно использовать их и отбросить их, почему у нас обоих вообще есть массив?
for i in restaurants.indices {
restaurant.location = findDistance(to: location)
}
Однако это все еще не очень хорошо.Тип данных Restaurant
страдает от двухэтапной инициализации, которая является запахом кода .Сначала мы инициализируем его с nil
местоположением, а затем устанавливаем его в реальное местоположение.Зачем беспокоиться?Почему бы нам просто не установить местоположение напрямую?
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),
]
Но это все же не хороший дизайн ...
Исправление недостатков с помощью findDistance(to:)
иRestaurant.distance
Что вообще делает findDistance(to:)
?Внутренне он проходит расстояние от какого-то жестко заданного, неназванного места CLLocation(latitude: 31.461512, longitude: 74.272024)
.Когда я говорю someRestaurant.distance
, я ожидаю расстояние до меня.Если бы это было расстояние от некоторой контрольной точки A, я бы ожидал, что API вместо этого будет написано что-то вроде someResaurant.distanceFromNorthPole
, или что-то в этом роде.
Более того, почему это работа Restaurant
хранить свое расстояние до чего-то еще?Что если я захочу restaurant.distanceToSouthPole
, restaurant.distanceToEquator
?API будет довольно раздутым, а тип restaurant
в конечном итоге будет делать слишком много.А что если я restaurant.distanceToMe
?Я могу двигаться, как предварительно вычисленное, сохраненное значение будет идти в ногу со мной, когда я двигаюсь?
Решение - не хранить расстояние вообще. Вместо этого предоставьте API, который пользователи этого типа данных могут использовать для вычисления расстояний до любой точки по своему выбору.
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)")
}
И удивительно, это все же не лучшее, что мы можем сделать это!
Не хранить двойники для латов и длинных
Вот для чего CLLocation
. Обратите внимание, что почти во всех случаях использования lat
и long
требуется сначала поместить его в CLLocation
. Так что давайте просто сохраним это, а не разделяем отдельные компоненты и передаем их независимо друг от друга Это предотвращает ошибки, такие как 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)
}
}
Однако, если вы сделаете это, инициализатору теперь потребуется CLLocation
вместо двух отдельных lat
/ long
Double
с. Это лучше для взаимодействия с API определения местоположения (где CLLocation
является типом «единой валюты» для обмена информацией о местоположении), но это более громоздко для жестко закодированных местоположений, таких как ваши рестораны, потому что все ваши вызовы инициализатора раздуваются кучей звонков на 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)),
]
Чтобы исправить это, мы можем убрать CLLocation.init(latitude:longitude:)
в маленький инициализатор для удобства. Я делаю это в расширении Restaurant
, а не непосредственно в начальном объявлении Restaurant
, потому что это сохраняет сгенерированный компилятором инициализатор (называемый "инициализатором для членов"), который в противном случае был бы заменен:
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),
]
Изменчивость
Названия и местоположения ресторанов вряд ли изменятся в течение срока действия экземпляра вашего приложения, поэтому нет необходимости сохранять их изменяемыми. Итак, давайте исправим это:
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
И наконец ...
Мы подошли к финальной стадии. Хорошо названный Restaurant
, который не страдает от необходимости двухэтапной инициализации, предоставляет актуальные данные о расстоянии для любых точек, которые могут понравиться пользователю, и который не уязвим к ошибкам копирования и вставки благодаря lat
и long
склеены в 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)")
}