英文:
Finding closest 20 minute interval from current time
问题
我有一个日期和时间的数组,如下所示:
[["2023年8月8日", "00:00", "302.218616", "-21.391909"],
["2023年8月8日", "00:20", "306.564766", "-24.382470"],
["2023年8月8日", "00:40", "311.149963", "-27.204929"]
...
每个索引递增20分钟,持续24小时。
我想找到一种方法来排序这些时间,以便我可以找到最接近当前时间的时间间隔(比如说现在是2:34,我想要找到包含2:40的索引,如果是2:29,我想要找到包含2:20的索引)。
我正在使用时间库将小时和分钟作为字符串获取,但我不确定如何将其与现有的时间进行比较。
英文:
I have an array of dates and times like so:
[["2023-Aug-08", "00:00", "302.218616", "-21.391909"],
["2023-Aug-08", "00:20", "306.564766", "-24.382470"],
["2023-Aug-08", "00:40", "311.149963", "-27.204929"]
...
Each index increments by 20 minutes and continues for 24 hours.
I want to find a way to sort through the times specifically so that I can find the closest interval to the current time (say its 2:34, I want the get the index that has 2:40 in it and if its 2:29 I am looking for 2:20).
I am using the time library to get the hours and minutes as a string but I'm not sure how I can use that to compare to the existing times.
答案1
得分: 2
在C++20中,我会使用<chrono>
库。不幸的是,实现仍在陆续上线中。您的供应商可能还没有实现它,或者可能有其他原因导致您还不能使用C++20。如果是这种情况,您可以使用这个免费的、开源的C++20 <chrono>
预览库,它可以轻松地移植到C++20 <chrono>
。
基本思路是使用std::chrono::round
函数将时间舍入到最近的20分钟间隔。但首先要做的是将数据从字符串形式解析成可以高效计算的数据结构:
#include <chrono>
#include <iostream>
#include <map>
#include <sstream>
std::pair<std::chrono::sys_time<std::chrono::minutes>, double>
parse_data(char const* date, char const* time, char const* data)
{
using namespace std;
using namespace chrono;
istringstream in{string{date} + ' ' + time + ' ' + data};
in.exceptions(ios::failbit);
sys_time<minutes> tp;
double item;
in >> parse("%Y-%b-%d %H:%M", tp) >> item;
return {tp, item};
}
这将解析成一个pair<sys_time<minutes>, double>
。double
只是一个代表与日期相关的数据的占位符。sys_time<minutes>
是一个以分钟精度表示UTC的日期时间。如果这是某个本地时间,您可以轻松地将sys_time
的类型更改为local_time
。
我选择在解析失败时抛出异常。也可以进行其他错误检查。
有了这个辅助函数,使用您的数据填充std::map
非常简单:
int
main()
{
using namespace std;
using namespace chrono;
char const* dates[][3] = {{"2023-Aug-08", "00:00", "1.0"},
{"2023-Aug-08", "00:20", "3.14"},
{"2023-Aug-08", "00:40", "2.71"}};
map<sys_time<minutes>, double> m;
for (auto const& i : dates)
m.insert(parse_data(i[0], i[1], i[2]));
...
}
- 声明您的
map
- 对于每一行数据,使用解析函数将一行插入到
map
中
现在您可以轻松地在map
中查找项目。首先声明一个长度为20分钟的duration
类型,以便您可以轻松地将时间舍入到该间隔:
using duodecamin = duration<minutes::rep,
ratio_multiply<ratio<20>, minutes::period>>;
现在,给定时间点,您可以将其舍入到最近的20分钟间隔,并在map
中查找该值:
auto now = sys_days{2023y/August/8} + 34min;
cout << m[round<duodecamin>(now)] << '\n';
now = sys_days{2023y/August/8} + 29min;
cout << m[round<duodecamin>(now)] << '\n';
输出:
2.71
3.14
now
也可以轻松地来自:
auto now = system_clock::now();
英文:
In C++20 I would use <chrono>
. Unfortunately implementations are still coming online. Your vendor may or may not have implemented it yet. Or there may be other reasons you can not yet use C++20. If this is the case you can use this free, open-source C++20 <chrono>
preview library which will easily port to C++20 <chrono>
.
The basic idea will be to use the std::chrono::round
function to round to the nearest 20min interval. But the first job is to parse your data out of string form into a data structure you can efficiently compute with:
#include <chrono>
#include <iostream>
#include <map>
#include <sstream>
std::pair<std::chrono::sys_time<std::chrono::minutes>, double>
parse_data(char const* date, char const* time, char const* data)
{
using namespace std;
using namespace chrono;
istringstream in{string{date} + ' ' + time + ' ' + data};
in.exceptions(ios::failbit);
sys_time<minutes> tp;
double item;
in >> parse("%Y-%b-%d %H:%M", tp) >> item;
return {tp, item};
}
This parses everything into a pair<sys_time<minutes>, double>
. double
is just a stand-in for whatever data you have associated with the date. sys_time<minutes>
is a minute-precision date-time that represents UTC. If this is some local time, you can trivially change the type of sys_time
to local_time
.
I've chosen to throw an exception if the parse fails. Other error checking is also possible.
With this helper function it is trivial to fill a std::map
with your data:
int
main()
{
using namespace std;
using namespace chrono;
char const* dates[][3] = {{"2023-Aug-08", "00:00", "1.0"},
{"2023-Aug-08", "00:20", "3.14"},
{"2023-Aug-08", "00:40", "2.71"}};
map<sys_time<minutes>, double> m;
for (auto const& i : dates)
m.insert(parse_data(i[0], i[1], i[2]));
...
- Declare your map
- For each row of data, use your parsing function to insert a row into the map
Now you can easily look up items in the map. First declare a duration type of length 20min so that you can easily round to it:
using duodecamin = duration<minutes::rep,
ratio_multiply<ratio<20>, minutes::period>>;
Now given timepoints you can round to the nearest 20min interval and look that value up in the map:
auto now = sys_days{2023y/August/8} + 34min;
cout << m[round<duodecamin>(now)] << '\n';
now = sys_days{2023y/August/8} + 29min;
cout << m[round<duodecamin>(now)] << '\n';
Output:
2.71
3.14
now
could have just as easily come from:
auto now = system_clock::now();
答案2
得分: 0
首先,你想将每个日期时间子列表解析为datetime
对象。关于如何做到这一点,有很多资源可供参考;这里是一个很好的起点。
一旦你将20分钟间隔转换为datetime
对象的列表,我们可以开始搜索算法。
下面是一个简单方法的初始草图。它为列表中的每个项创建一个“距离”值,然后使用index
和min
来获取最小值的索引。
import datetime
def find_closest(datetimes, when):
distances = [abs((dt - when).total_seconds()) for dt in datetimes]
return datetimes[distances.index(min(distances))]
然而,这不是一个很好的算法。你看,它必须计算每个项的距离,然后进行线性搜索以找出最小值。这意味着要两次遍历整个列表!
但是对于排序后的列表,可以使用一种类似二分搜索的方法在非常大的列表上获得更好的性能。
import datetime
def find_closest(datetimes: list[datetime.datetime], when: datetime.datetime):
"Finds and returns the datetime in `datetimes` that is closest to `when`."
datetimes = sorted(datetimes)
low = 0
high = len(datetimes) - 1
while high - low > 1:
center = (high + low) // 2
here = datetimes[center]
distance = abs((here - when).total_seconds())
# Print below is to demonstrate the algorithm - feel free to comment out.
print(
f"High {high}, low {low}, center {center} - time at center is {here}, offset of {distance}"
)
if distance == 0:
print("Exact match found")
return here
elif when > here:
# when is later so look later on
low = center
else:
high = center
# No datetime is exactly equal to 'when'.
dt_low = datetimes[low]
dt_high = datetimes[low]
if abs((dt_low - when).total_seconds()) < abs((dt_high - when).total_seconds()):
print("Low is closest")
return dt_low
else:
print("High is closest")
return dt_high
>>> now = datetime.datetime.now()
>>> dts = [datetime.datetime.now() + datetime.timedelta(seconds=n * 20 - 5) for n in range(-2, 6)]
>>> for d in dts: print(d)
...
2023-08-08 19:49:52.401911
2023-08-08 19:50:12.401925
2023-08-08 19:50:32.401926
2023-08-08 19:50:52.401927
2023-08-08 19:51:12.401928
2023-08-08 19:51:32.401929
2023-08-08 19:51:52.401930
2023-08-08 19:52:12.401934
>>> find_closest(dts, now)
High 7, low 0, center 3 - time at center is 2023-08-08 19:50:52.401927, offset of 22.224106
High 3, low 0, center 1 - time at center is 2023-08-08 19:50:12.401925, offset of 17.775896
High 3, low 1, center 2 - time at center is 2023-08-08 19:50:32.401926, offset of 2.224105
High is closest
datetime.datetime(2023, 8, 8, 19, 50, 12, 401925)
>>> next_dt = now + datetime.timedelta(seconds=80)
>>> find_closest(dts, next_dt)
High 7, low 0, center 3 - time at center is 2023-08-08 19:50:52.401927, offset of 57.775894
High 7, low 3, center 5 - time at center is 2023-08-08 19:51:32.401929, offset of 17.775892
High 7, low 5, center 6 - time at center is 2023-08-08 19:51:52.401930, offset of 2.224109
High is closest
datetime.datetime(2023, 8, 8, 19, 51, 32, 401929)
>>> next_dt - now
datetime.timedelta(seconds=80)
>>> find_closest(dts, next_dt) - find_closest(dts, now)
(...)
datetime.timedelta(seconds=80, microseconds=4)
>>> # (the takeaway is that floating points WILL make you age faster.)
当然,除非你有大量的数据需要处理,并且性能是一个问题,否则更简单的线性搜索方法可能更好,因为它的代码更少,更容易理解。
此外,也可能有更好的方法,采用更数值化的方法,如@user4581301所建议的,使用timedelta
结合除法、四舍五入和乘法,利用四舍五入将时间量化为20分钟的网格。但这种方法的优点是它适用于任何间隔不均匀的日期时间列表,即使间隔不是20分钟,它仍然有效。
英文:
First off, you want to parse each of those date-time sublists into a datetime
object. There's plenty resources on how to do things like that out there;. here is a good start.
Once you have your 20 minute intervals turned into a list of datetime
objects, we can have the search algorithm.
Here's an initial sketch for a naive approach. It makes a "distance" value from each item in the list, then uses index
and min
to get the index of the minimum value.
import datetime
def find_closest(datetimes, when):
distances = [abs((dt - when).total_seconds()) for dt in datetimes]
return datetimes[distances.index(min(distances))]
However, this is not a very good algorithm. You see, it has to compute the distance for every single item, and then do a linear search to figure out which is the smallest. This means iterating over the full list twice!
But with a sorted list, it is possible to get better performance on very large lists with a sort of binary search.
import datetime
def find_closest(datetimes: list[datetime.datetime], when: datetime.datetime):
"Finds and returns the datetime in `datetimes` that is closest to `when`."
datetimes = sorted(datetimes)
low = 0
high = len(datetimes) - 1
while high - low > 1:
center = (high + low) // 2
here = datetimes[center]
distance = abs((here - when).total_seconds())
# Print below is to demonstrate the algorithm - feel free to comment out.
print(
f"High {high}, low {low}, center {center} - time at center is {here}, offset of {distance}"
)
if distance == 0:
print("Exact match found")
return here
elif when > here:
# when is later so look later on
low = center
else:
high = center
# No datetime is exactly equal to 'when'.
dt_low = datetimes[low]
dt_high = datetimes[low]
if abs((dt_low - when).total_seconds()) < abs((dt_high - when).total_seconds()):
print("Low is closest")
return dt_low
else:
print("High is closest")
return dt_high
>>> now = datetime.datetime.now()
>>> dts = [datetime.datetime.now() + datetime.timedelta(seconds=n * 20 - 5) for n in range(-2, 6)]
>>> for d in dts: print(d)
...
2023-08-08 19:49:52.401911
2023-08-08 19:50:12.401925
2023-08-08 19:50:32.401926
2023-08-08 19:50:52.401927
2023-08-08 19:51:12.401928
2023-08-08 19:51:32.401929
2023-08-08 19:51:52.401930
2023-08-08 19:52:12.401934
>>> find_closest(dts, now)
High 7, low 0, center 3 - time at center is 2023-08-08 19:50:52.401927, offset of 22.224106
High 3, low 0, center 1 - time at center is 2023-08-08 19:50:12.401925, offset of 17.775896
High 3, low 1, center 2 - time at center is 2023-08-08 19:50:32.401926, offset of 2.224105
High is closest
datetime.datetime(2023, 8, 8, 19, 50, 12, 401925)
>>> next_dt = now + datetime.timedelta(seconds=80)
>>> find_closest(dts, next_dt)
High 7, low 0, center 3 - time at center is 2023-08-08 19:50:52.401927, offset of 57.775894
High 7, low 3, center 5 - time at center is 2023-08-08 19:51:32.401929, offset of 17.775892
High 7, low 5, center 6 - time at center is 2023-08-08 19:51:52.401930, offset of 2.224109
High is closest
datetime.datetime(2023, 8, 8, 19, 51, 32, 401929)
>>> next_dt - now
datetime.timedelta(seconds=80)
>>> find_closest(dts, next_dt) - find_closest(dts, now)
(...)
datetime.timedelta(seconds=80, microseconds=4)
>>> # (the takeaway is that floating points WILL make you age faster.)
Of course, unless you have tons of data to process and performance is a concern here, the simpler linear search approach is probably better, as it is way less code and easier to understand.
Also there's probably a better way with a more numerical approach like @user4581301 has suggested, using timedelta
alongside with division, rounding, and multiplication, using rounding to your advantage to quantize the time to the 20 minute grid. But this approach has the upside that it works in general with any list of datetimes with any spacing, even irregular spacings, so if your datetimes
isn't evenly spaced in 20 minute intervals it'd still work.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论