获取最接近的 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) -&gt; Bool 是一个函数,如果根据浮点运算将 ab 的值视为相等,则返回 true。有关更多信息,请参阅 this post - 但请注意,您应该将 epsilon 调整为您认为合适的值。


这些解决方案的复杂度为 O(n)。

最后一点:如果您有一个已经排序的数组,正如其他答案所提到的,您可以利用此属性使用二分搜索找到您想要的内容。

【讨论】:

以上是关于获取最接近的 CGFloat 值的主要内容,如果未能解决你的问题,请参考以下文章

最大 CGFloat 值是不是有常数?

关于在 Swift 4 中返回 CGFloat 值

根据随机 CGFloat 值重新绘制 UIBezierPath

无法将'CGFloat'类型的值转换为闭包结果类型'CGPoint'

swift 3 中 swift 2 的 CGFloat.random(min: CGFloat, max: CGFloat) 是啥?

UIEdgeInsetsMake(CGFloat top, CGFloat left, CGFloat bottom, CGFloat right)