获取最接近的 CGFloat 值

Get the most closest value of the CGFloat

提问人:topgun 提问时间:11/25/2019 更新时间:11/26/2019 访问量:573

问:

我很好奇 swift 中是否有办法通过他们的现代 api 实现最接近的价值?

例如:

let x = [1.2, 3.4, 4.5, 6.7, 8.9]
print(x.getClosestValue(3.7) //3.4

我一直在玩map和reduce,但仍然无法解决这个问题。问题是我必须遍历整个数组才能检测误报。在某些情况下,您可以拥有多个最接近的值,所以只是想知道如何快速完成这项工作?

iOS 阵列 swift swift3 swift2

评论

1赞 Moayad Al kouz 11/25/2019
检查此答案: stackoverflow.com/a/51806138/8476915
1赞 m1sh0 11/25/2019
列表总是排序的吗?
1赞 topgun 11/27/2019
是的,此列表始终是排序的。
0赞 Marcel Alves 11/27/2019
如果列表是排序的,你可以使用我在回答中提到的方法,但你也可以使用分而治之的策略更有效地做到这一点,比如二叉搜索。您想知道所有最接近值的范围还是只有一个最接近值的范围?我可以提供更多关于如何实施这种分而治之的方法的细节——不过,这需要几行。

答:

-1赞 Developer Sheldon 11/25/2019 #1

目前还没有来自 Apple 的直接 api 可供使用。 如果你不关心时间,你可以对数组进行排序,并使用数组的或方法进行线性搜索。 但是,您可以通过排序和使用二叉搜索来使其变得更好,它可能只需要您额外的 20 行?first(where:)last(where:)

评论

0赞 Alexander 11/26/2019
sorted()+ first(where:)有效,但它是.只需使用 、 即可实现,甚至不需要实现二进制搜索。当然,二叉搜索是最好的,实现(假设输入已经排序)O(n*log(n))O(n)min(by:)max(by:)O(log(n))
0赞 Developer Sheldon 11/27/2019
@Alexander-ReinstateMonica,为什么你可以假设你的输入是排序的?当然,我首先对其进行了排序,因为我不认为它已排序。哈哈。
0赞 Alexander 11/27/2019
min(by:)/max(by:)不需要排序。您建议使用或二进制搜索需要排序。first(where:)
0赞 Developer Sheldon 12/4/2019
@Alexander-ReinstateMonica 感谢您大声疾呼。我不得不承认我的解决方案非常糟糕。哈哈
4赞 Joakim Danielson 11/26/2019 #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)})
0赞 Evan Deaubl 11/26/2019 #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
}

评论

0赞 Alexander 11/27/2019
当存在其他更适合的功能时,不要滥用。在本例中,您正在执行操作。github.com/amomchilov/Blog/blob/master/......reducemax(by:)
1赞 Marcel Alves 11/26/2019 #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 作为答案(如果您对此主题感兴趣,可以查看此博客文章)。


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]

这里的区别在于,我们用来查找与目标相等距离的所有值。可能会有重复的值,如果您愿意,可以使用 Set 消除这些值。filter()

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]

注意:是一个函数,如果根据浮点运算,值 和 被认为是相等的,则返回。有关更多信息,请参阅此帖子 - 但请注意,您应该将 epsilon 调整到您认为合适的值。isDoubleEqual(a: Double, b: Double) -> Booltrueab


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

最后一点:如果你有一个已经排序的数组,正如其他答案所提到的,你可以利用这个属性,使用二进制搜索来找到你想要的东西。