【问题标题】:Append array data into Struct variable将数组数据附加到 Struct 变量中
【发布时间】:2019-04-29 03:38:27
【问题描述】:

我想将数组的数据合并到我的结构变量中

下面是代码

我想在休息时添加距离数组值 -> 距离

distance = ["12.44","45.32","56.1","54.22"]

将此距离数组合并到结构变量距离中

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
}

【问题讨论】:

  • 合并是什么意思?您是否只想为数组中的每个餐厅设置距离?
  • distance 应该是[Double]
  • 对于不同的问题,这似乎是一个糟糕的解决方案。您希望源以及到它的距离保持不变,这意味着可以在 Restaurants 初始化程序中计算距离,或者您希望不断更新 source 并每次重新计算距离。然后将distance 存储在结构中就没有意义了。
  • @Sulthan 完全正确。这就是为什么我花了将近一个小时来写我的大量答案,一点一点地分解它。

标签: swift


【解决方案1】:

这里发生了很多事情,所以我将引导您完成解决方案。

首先,rest 是一个糟糕的变量名。当我第一次阅读它时,我认为这是“其余部分”,就像某些东西的其余部分一样,并且正在寻找“主要”数据的位置。击键不花钱,只需输入restaurants即可。

其次,您创建一个空数组,并手动将所有这些餐馆添加到其中。相反,您可以直接从包含所有餐厅的数组文字创建一个数组。这省略了分开的需要

要回答您的直接问题,您可以使用zip 一起迭代restdistance,但问题是rest1rest2、...变量,它们注定要失败。当您复制/粘贴一堆行并忘记更改其中一个时会发生什么,不小心写了rest.append(rest6); rest.append(rest6),忘记了rest7

第三,您的findDistance(from:long:) 函数需要两个Doubles(一个纬度和一个经度),并使用它们构造一个CLLocation。但是当您查看此函数的使用位置时,您已经有了一个CLLocation,您将其分解为两个Doubles,只是为了findDistance(from:long:) 立即将它们重新组合成一个CLLocation。相反,只需让findDistance 直接使用CLLocation

第四,Restaurants 数据类型是未命名的。这不是多个餐厅。它模拟了一家餐厅。相应地命名: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 并行迭代restaurantsdistances,然后从每个“距离”中改变每个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 数据类型遭受两阶段初始化,即code smell。首先,我们使用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.distanceToSouthPolerestaurant.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)")

}

令人惊讶的是,这仍然不是我们能做到的最好的!

不要为 lats 和 longs 存储双打

这就是CLLocation 的用途。请注意,几乎所有latlong 的使用都需要首先将其装箱成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/longDoubles。这更适合与位置 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,它不需要两阶段初始化,它为用户可能喜欢的任何点提供最新的距离数据,并且由于@,它不易受到复制粘贴错误的影响987654390@ 和 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)")

}

【讨论】:

  • 我需要数组中的距离和其他值,如“名称、纬度、长距离和距离??”
  • 我想根据最近的距离来排列数组,我试图添加到该 Restaurant Struct 中,所以我可以用最近的餐厅填充 tableview。
  • @BilalNawaz 我的整个帖子的重点是您不需要(也不应该将其存储在数组中)。只需在需要时计算它。因此,在您的 tableview 代表的cellForRowAtIndexPath 中,您可以使用let restaurant = restaurents[row] 将特定餐厅从您的数组中取出,然后访问restaurant.namerestaurant.location.latituderestaurant.location.longituderestaurant.distance(from: myLocation)。这样你的距离总是最新的,而不是很久以前存储在数组中的一些陈旧的距离值。
  • 还有一个问题,我该如何安排餐厅以便最近的餐厅首先出现?
  • @BilalNawaz 按距离对它们进行排序:restaurants.sorted { $0.distance(from: myLocation) < $1.distance(from: myLocation) }
【解决方案2】:

如果我认为您的问题是正确的,您想用您的 distance 变量填写 rest 中每个餐厅的 distance 属性。另外,假设distance 变量和rest 变量的计数相等,你可以这样做,

if rest.count == distance.count {
   (0..<rest.count).forEach {
       rest[$0].distance = distance[$0]
   }
}

【讨论】:

  • rest.count != distance 计数时,这会默默地失败(什么都不做),这很危险。最好抛出一个表明存在问题的错误。此外,如果您要遍历数组的索引,只需直接这样做:rest.indices.forEach { i in ...。使用0..&lt;rest.count 为数组手动构造一系列索引很容易出错(以防您不小心写了0...rest.count),并且不适用于数组切片和许多其他集合。
猜你喜欢
  • 2016-02-20
  • 2018-12-08
  • 2021-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-21
  • 2018-09-27
  • 2018-11-09
相关资源
最近更新 更多