topgun
topgun

Reputation: 2573

Get the most closest value of the CGFloat

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

Answers (4)

Marcel Alves
Marcel Alves

Reputation: 496

I can imagine some different scenarios, so I will try to address most of them.


1- You want to find only one number:

1.1 - Finding the actual number:

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.

1.2 - Finding the index:

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).


2- You want to find all the closest numbers:

Since you've mentioned that there can be multiple numbers, I think this would be the method of your choice.

2.1 - Finding all closest numbers:

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.

2.2 - Finding the indices of all closest numbers:

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

Evan Deaubl
Evan Deaubl

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

Joakim Danielson
Joakim Danielson

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

Developer Sheldon
Developer Sheldon

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

Related Questions