OXYGENHUB
◆ main openmsx / src / utils / find_closest.h
2.3 KiB · Objective-C · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef FIND_CLOSEST_H
2
#define FIND_CLOSEST_H
3
4
#include <algorithm>
5
#include <functional>
6
#include <iterator>
7
#include <ranges>
8
#include <type_traits>
9
#include <utility>
10
11
// Find the element in a sorted range that is closest to the requested value.
12
// @pre: is_sorted(range, comp, proj)
13
//
14
// The return value contains:
15
// * An iterator pointing at the closest element (or end() when the range is
16
//   empty). When two neighboring elements are equally close, the earlier
17
//   element is returned.
18
// * The absolute distance between that element and the requested value. When
19
//   the range is empty the distance is unspecified.
20
template<std::bidirectional_iterator I, std::sentinel_for<I> S, typename T,
21
         typename Compare = std::ranges::less, typename Proj = std::identity>
22
[[nodiscard]] constexpr auto find_closest(
23
	I first, S last, const T& value, Compare comp = {}, Proj proj = {})
24
{
25
	using Ref = std::iter_reference_t<I>;
26
	using Key = std::remove_cvref_t<std::invoke_result_t<Proj&, Ref>>;
27
	using LeftDiff = std::remove_cvref_t<decltype(std::declval<Key>() - std::declval<T>())>;
28
	using RightDiff = std::remove_cvref_t<decltype(std::declval<T>() - std::declval<Key>())>;
29
	static_assert(std::is_same_v<LeftDiff, RightDiff>,
30
	              "find_closest() requires symmetric subtraction results.");
31
	using Diff = LeftDiff;
32
	using Result = std::pair<I, Diff>;
33
34
	auto it = std::ranges::lower_bound(first, last, value, comp, proj);
35
36
	if (it == first) {
37
		if (it == last) return Result{last, Diff{}};
38
		return Result{it, std::invoke(proj, *it) - value};
39
	}
40
	if (it == last) {
41
		auto prev = std::prev(last);
42
		return Result{prev, value - std::invoke(proj, *prev)};
43
	}
44
45
	auto prev = std::prev(it);
46
	auto distPrev = value - std::invoke(proj, *prev);
47
	auto distCurr = std::invoke(proj, *it) - value;
48
	return (distPrev <= distCurr) ? Result{prev, distPrev}
49
	                              : Result{it, distCurr};
50
}
51
52
template<std::ranges::bidirectional_range Range, typename T,
53
         typename Compare = std::ranges::less, typename Proj = std::identity>
54
[[nodiscard]] constexpr auto find_closest(
55
	Range&& range, const T& value, Compare comp = {}, Proj proj = {})
56
{
57
	auto first = std::ranges::begin(range);
58
	auto last = std::ranges::end(range);
59
	return find_closest(first, last, value, comp, proj);
60
}
61
62
#endif