获取最接近的 CGFloat 值
Posted
技术标签:
【中文标题】获取最接近的 CGFloat 值【英文标题】:Get the most closest value of the CGFloat 【发布时间】:2020-03-20 22:44:40 【问题描述】:我很好奇是否有办法通过他们的现代 api 快速实现最接近的价值?
例如:
let x = [1.2, 3.4, 4.5, 6.7, 8.9]
print(x.getClosestValue(3.7) //3.4
我一直在玩 map 和 reduce,但仍然无法解决这个问题。出现的问题是我必须遍历整个数组来检测误报。在某些情况下,您可以有多个最接近的值,所以只是想知道如何快速完成此操作?
【问题讨论】:
查看这个答案:***.com/a/51806138/8476915 列表总是排序的吗? 是的,这个列表总是排序的。 如果列表已排序,您可以使用我在回答中提到的方法,但您也可以使用分而治之的策略更有效地做到这一点,例如二分搜索。您想知道所有最接近的值还是一个最接近的值的范围?我可以提供更多关于如何实现这种分而治之的方法的细节——不过这需要几行代码。 【参考方案1】:目前还没有来自 Apple 的直接 API 可供使用。
如果不在意时间,可以对数组进行排序,使用Array的first(where:)
或last(where:)
方法进行线性搜索。
但是,您可以通过排序和使用二分搜索来使其变得更好,它可能只需要额外的 20 行?
【讨论】:
sorted()
+ first(where:)
有效,但它是 O(n*log(n))
。只需使用min(by:)
、max(by:)
就可以实现O(n)
,甚至不需要实现二分查找。当然,二分查找是最好的,实现O(log(n))
(假设输入已经排序)
@Alexander-ReinstateMonica 为什么您可以假设您的输入已排序?我当然首先对其进行了排序,因为我不认为它已排序。大声笑。
min(by:)
/max(by:)
不需要排序。您建议使用first(where:)
或二分搜索 需要排序。
@Alexander-ReinstateMonica 感谢您的关注。我不得不承认我的解决方案很糟糕。大声笑【参考方案2】:
您可以使用min(by:)
来实现这一点,它不需要排序数组
let x = [1.2, 3.4, 4.5, 6.7, 8.9]
let target = 3.7
let closestTarget = x.min(by: abs($0 - target) < abs($1 - target))
【讨论】:
【参考方案3】:这个使用reduce(_:_:)
的解决方案应该可以工作:
let x = [1.2, 3.4, 4.5, 6.7, 8.9]
let target = 4.7
// assumes x is non-empty array
let closestTarget = x.reduce(x[0]) closest,val in
abs(target - closest) > abs(target - val) ? val : closest
【讨论】:
当存在其他更合适的函数时,不要滥用reduce
。在这种情况下,您正在执行max(by:)
操作。 github.com/amomchilov/Blog/blob/master/…【参考方案4】:
我可以想象一些不同的场景,所以我会尝试解决其中的大部分。
1- 你只想找到一个数字:
1.1 - 查找实际数字:
你可以使用min(by:)
:
let x = [1.2, 4.0, 3.4, 6.7, 8.9]
let target = 3.7
let closestValue = x.min abs($0 - target) < abs($1 - target)
print(closestValue) // prints Optional(4.0)
这种方法是最直接的。您将得到返回数组元素和目标之间减法最小值的结果。
1.2 - 查找索引:
您也可以使用min(by:)
,但首先,您需要获取数组的枚举版本来获取索引。
let x = [1.2, 4.0, 3.4, 6.7, 8.9]
let target = 3.7
let closestIdx = x.enumerated().min abs($0.1 - target) < abs($1.1 - target) !.0
print(closestIdx) // prints 1
注意:尽管 3.4 与 3.7 和 4.0 的距离一样远,但由于浮点运算,这种方法将始终返回 4.0 作为答案(如果您有兴趣,可以查看 this blog post在本主题中)。
2- 你想找到所有最接近的数字:
既然您提到可以有多个数字,我认为这将是您选择的方法。
2.1 - 查找所有最接近的数字:
let x = [1.2, 3.4, 4.0, 6.7, 8.9]
let target = 3.7
let minDiff = x.map return abs($0 - target) .min()!
let closestValues = x.filter isDoubleEqual(a: $0, b: target - minDiff) || isDoubleEqual(a: $0, b: target + minDiff)
print(closestValues) // prints [3.4, 4.0]
这里的区别在于我们使用filter()
来查找与目标距离相等的所有值。可能存在重复值,如果您愿意,可以使用 Set 消除这些值。
2.2 - 查找所有最接近数字的索引:
再次使用enumerated()
获取索引的想法相同。
let x = [1.2, 3.4, 4.0, 6.7, 8.9]
let target = 3.7
let minDiff = x.map return abs($0 - target) .min()!
let tuples = x.enumerated().filter isDoubleEqual(a: $0.1, b: target - minDiff) || isDoubleEqual(a: $0.1, b: target + minDiff)
let closestIndices = tuples.map return $0.0
print(closestIndices) // prints [1, 2]
注意: isDoubleEqual(a: Double, b: Double) -> Bool
是一个函数,如果根据浮点运算将 a
和 b
的值视为相等,则返回 true
。有关更多信息,请参阅 this post - 但请注意,您应该将 epsilon 调整为您认为合适的值。
这些解决方案的复杂度为 O(n)。
最后一点:如果您有一个已经排序的数组,正如其他答案所提到的,您可以利用此属性使用二分搜索找到您想要的内容。
【讨论】:
以上是关于获取最接近的 CGFloat 值的主要内容,如果未能解决你的问题,请参考以下文章
根据随机 CGFloat 值重新绘制 UIBezierPath
无法将'CGFloat'类型的值转换为闭包结果类型'CGPoint'
swift 3 中 swift 2 的 CGFloat.random(min: CGFloat, max: CGFloat) 是啥?
UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)