Reputation: 2573
I am curious if there is a way in swift to achieve the most closest value via their modern api's?
For example:
let x = [1.2, 3.4, 4.5, 6.7, 8.9]
print(x.getClosestValue(3.7) //3.4
I have been playing around with map and reduce but still not be able to crack this problem. The problem occur's that I have to iterate through entire array to detect false positives as well. There can be a scenario where you can have multiple closest values, so was just wondering how this can be done in swiftly way?
Upvotes: 2
Views: 750
Reputation: 496
I can imagine some different scenarios, so I will try to address most of them.
You can use 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)
This approach is the most straightforward. You will get the result that returns the minimum value of the subtraction between the array element and the target.
You can also use min(by:)
, but first, you get the enumerated version of the array to get the indices.
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
Note: Even though 3.4 is equally distant to 3.7 as 4.0, this approach will always return 4.0 as the answer because of floating-point arithmetic (you can check this blog post if you're interested in this topic).
Since you've mentioned that there can be multiple numbers, I think this would be the method of your choice.
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]
The difference here is that we use filter()
to find all the values that are equally distant to a target. There may be repeated values, which can be eliminated using a Set if you wish.
The same idea of using enumerated()
again to take the indices.
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]
Note: isDoubleEqual(a: Double, b: Double) -> Bool
is a function that returns true
if the values a
and b
are considered equal according to floating-point arithmetic. See this post for more information - but note that you should adjust the epsilon to a value that you find suitable.
The complexity of these solutions is O(n).
A final note: if you have an array that is already sorted, as mentioned by other answers, you can take advantage of this property to find what you want using a binary search.
Upvotes: 1
Reputation: 719
This solution using reduce(_:_:)
should work:
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
}
Upvotes: 0
Reputation: 51993
You can use min(by:)
to achieve this and it doesn't require a sorted array
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)})
Upvotes: 5
Reputation: 2160
There is no straight api from Apple to use yet.
If you don't care about the time, you can sort the array and use Array's first(where:)
or last(where:)
methods to do linear search.
However, you can make it better by sort and using binary search, it will probably just take you additional 20 lines?
Upvotes: -1