简体   繁体   中英

Algorithm to find the closest time

I have a list of DateTime values with dates that contain hours and minutes:

List<DateTime> times = times = new List<DateTime>()
{
    new DateTime(2019, 01, 01, 17, 00, 00),
    new DateTime(2019, 01, 01, 18, 45, 00),
    new DateTime(2019, 01, 01, 19, 00, 00),
    new DateTime(2019, 01, 01, 19, 30, 00),
    new DateTime(2019, 01, 01, 22, 30, 00)
};

DateTime current = DateTime.Now;

I put them all in a ComboBox, and I want to make some sort of algorithm so when I load my form, it will check for the current time and find the closest value to the current time and select the ComboBox item that contains that hour.

How can I achieve this? I tried to loop through them all and check for the least hour, but that doesn't seem to work. Is there a smarter way to do it?

For example: If the current time is 17:32, it will choose 17:00, because that's the closest. But, if the current time is 18:20, it will choose 18:45 and so on.

Compare to the Ticks property of DateTime ( MSDN ). It can be seen as a linear representation of the whole date and timestamp and is sortable.

Do something like

comboBox.SelectedItem = times.OrderBy(t => Math.Abs(t.Ticks - current.Ticks)).First()

您可以使用DateTime.Now作为所有日期时间的差异,按此差异排序并得出第一个结果。

times.OrderBy(m => Math.Abs((DateTime.Now - m).TotalMilliseconds)).First();

You would have to select an instance of DateTime which minimizes the temporal distance to the current time. You could use an extension method for IEnumerable<T> to do that as follows.

public static T ArgMin<T, R>(T t1, T t2, Func<T, R> f)
                where R : IComparable<R>
{
    return f(t1).CompareTo(f(t2)) > 0 ? t2 : t1;
}

public static T ArgMin<T, R>(this IEnumerable<T> Sequence, Func<T, R> f)
                where R : IComparable<R>
{
    return Sequence.Aggregate((t1, t2) => ArgMin<T, R>(t1, t2, f));
}

var iNow = DateTime.Now;
var iResult = times.ArgMin(iTime => Math.Abs((iTime - iNow).Ticks));

Although very generic, this implementation does not involve any sorting.

You are looking for ArgMax which is not implemented in standard Linq , but can be emulated via Aggreagte

  List<DateTime> times = new List<DateTime>() {
    new DateTime(2019, 01, 01, 17, 00, 00),
    new DateTime(2019, 01, 01, 18, 45, 00),
    new DateTime(2019, 01, 01, 19, 00, 00),
    new DateTime(2019, 01, 01, 19, 30, 00),
    new DateTime(2019, 01, 01, 22, 30, 00),
  };

  DateTime toFind = new DateTime(2019, 5, 8, 18, 20, 0);

  var closestTime = times
    .Aggregate((best, item) => Math.Abs((item.TimeOfDay - toFind.TimeOfDay).Ticks) < 
                               Math.Abs((best.TimeOfDay - toFind.TimeOfDay).Ticks) 
       ? item 
       : best);

Please, note, that if we should find the closest time , we have to get rid of date part - TimeOfDay . If date part should be count, just remove TimeOfDay -

  var closestDateAndTime = times
    .Aggregate((best, item) => Math.Abs((item - toFind).Ticks) < 
                               Math.Abs((best - toFind).Ticks) 
       ? item 
       : best);

One option is to use MoreLinq's MinBy :

var actualNow = DateTime.Now;
// set `current` up however you need it
var current = new DateTime(2019, 01, 01, actualNow.Hour, actualNow.Minute, actualNow.Minute, actualNow.Millisecond); // set this up however you need it
var min = times.MinBy(z => Math.Abs((current - z).Ticks)).First();

It avoids the memory pressure of the OrderBy based solutions (since it avoids allocating a list to store the entire set of times ).

Note you may want to check whether times is empty before using this (or other) solutions. If you don't wish to do that, consider using:

var min = times.MinBy(z => Math.Abs((current - z).Ticks)).Cast<DateTime?>().FirstOrDefault();

which will return null (rather than default(DateTime) , or throw an exception) if times is empty.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM