使用 JavaScript 在数组中查找最近的日期

Posted

技术标签:

【中文标题】使用 JavaScript 在数组中查找最近的日期【英文标题】:Find closest date in array with JavaScript 【发布时间】:2012-08-01 11:24:24 【问题描述】:

我有一个包含天数的数组。每一天都是一个对象,例如:

day_year: "2012", day_month: "08", day_number: "03", day_name: "mon"

我还为每一天的对象添加了一个时间戳属性,方法是:

function convertDays() 
    var max_i = days.length;
    for(var i = 0; i < max_i; i++) 
        var tar_i = days[i];
        tar_i.timestamp = new Date(tar_i.day_year, tar_i.day_month, tar_i.day_number);
    

数组中的日期是任意的,因此它们没有真正的逻辑。

现在我想找到离任何给定日期最近的两天。因此,如果包含天数的数组包含

2012 年 8 月 2 日 2012 年 8 月 4 日 2012 年 8 月 23 日

我搜索 2012 年 8 月 11 日,我希望它返回 2012 年 8 月 4 日和 2012 年 8 月 23 日。

我尝试使用另一个问题的答案,如下所示:

function findClosest(a, x) 
    var lo, hi;
    for(var i = a.length; i--;) 
        if(a[i] <= x && (lo === undefined || lo < a[i])) lo = a[i];
        if(a[i] >= x && (hi === undefined || hi > a[i])) hi = a[i];
    
    return [lo, hi];

但是,这会返回 unidentified

实现这一目标最有效(处理器/内存占用最少的方式)是什么?

编辑:“但是,这些结果有多“奇怪”?您能否提供一个代码和数据示例?”

我现在使用以下内容来生成日期数组:

var full_day_array = [];
for(var i = 0; i < 10; i++) 
    var d = new Date();
    d.setDate(d.getDate() + i);
    full_day_array.push(day_year: d.getFullYear().toString(), day_month: (d.getMonth() + 1).toString(), day_number: d.getDate().toString());

奇怪的是,使用下面的代码,这仅适用于 10 个或更短日期的数组。每当我使用 11 个或更多日期的数组时,结果都会出乎意料。

例如:使用包含 15 个日期的数组,从 2012 年 8 月 6 日到 2012 年 8 月 21 日。如果我随后调用 findClosest(full_day_array, new Date("30/07/2012");,您会期望它返回 nextIndex: 0, prevIndex: -1。但是,它返回nextIndex: 7, prevIndex: -1。为什么?

function findClosest(objects, testDate) 
    var nextDateIndexesByDiff = [],
        prevDateIndexesByDiff = [];

    for(var i = 0; i < objects.length; i++) 
        var thisDateStr = [objects[i].day_month, objects[i].day_number, objects[i].day_year].join('/'),
            thisDate    = new Date(thisDateStr),
            curDiff     = testDate - thisDate;

        curDiff < 0
            ? nextDateIndexesByDiff.push([i, curDiff])
            : prevDateIndexesByDiff.push([i, curDiff]);
    

    nextDateIndexesByDiff.sort(function(a, b)  return a[1] < b[1]; );
    prevDateIndexesByDiff.sort(function(a, b)  return a[1] > b[1]; );


    var nextIndex;
    var prevIndex;

    if(nextDateIndexesByDiff.length < 1) 
        nextIndex = -1;
     else 
        nextIndex = nextDateIndexesByDiff[0][0];
    
    if(prevDateIndexesByDiff.length < 1) 
        prevIndex = -1;
     else     
        prevIndex = prevDateIndexesByDiff[0][0];
    
    return nextIndex: nextIndex, prevIndex: prevIndex;

【问题讨论】:

你为什么用那种格式?您能否将格式更改为时间戳,以便更轻松地将它们解析为正确的日期对象? 我确实可以将它们更改为时间戳,例如循环遍历这些天并向它们添加一个属性“时间戳”:days[i].timestamp = new Date(days[i].day_year, days[i].day_month, days[i].day_number); 这有帮助吗? 如果你想谈论效率,那么你应该定义你想如何使用它。例如,您是否需要在一组日期中找到一个最接近的日期,或者您将拥有相同的一组日期并且您将对其进行多次搜索,这一点很重要。 我最多有五组天,每组包含大约 200 天。然后我有多达 100 个日期,我必须找到最近的两天。 应该是typeof lo == "undefined" 【参考方案1】:

如果您使用 Date 对象数组而不是您的自定义结构,则可以在 O(N) 中轻松实现:

var testDate = new Date(...);
var bestDate = days.length;
var bestDiff = -(new Date(0,0,0)).valueOf();
var currDiff = 0;
var i;

for(i = 0; i < days.length; ++i)
   currDiff = Math.abs(days[i] - testDate);
   if(currDiff < bestDiff)
       bestDate = i;
       bestDiff = currDiff;
      


/* the best date will be days[bestDate] */

如果对数组进行排序,则可以通过二分查找在 O(log N) 内实现。

编辑:“重要的是我找到最接近的匹配beforeafter日期”

var testDate = new Date(...);

var bestPrevDate = days.length;
var bestNextDate = days.length;

var max_date_value = Math.abs((new Date(0,0,0)).valueOf());

var bestPrevDiff = max_date_value;
var bestNextDiff = -max_date_value;

var currDiff = 0;
var i;

for(i = 0; i < days.length; ++i)
   currDiff = testDate - days[i].the_date_object;
   if(currDiff < 0 && currDiff > bestNextDiff)
   // If currDiff is negative, then testDate is more in the past than days[i].
   // This means, that from testDate's point of view, days[i] is in the future
   // and thus by a candidate for the next date.
       bestNextDate = i;
       bestNextDiff = currDiff;
   
   if(currDiff > 0 && currDiff < bestPrevDiff)
   // If currDiff is positive, then testDate is more in the future than days[i].
   // This means, that from testDate's point of view, days[i] is in the past
   // and thus by a candidate for the previous date.
       bestPrevDate = i;
       bestPrevDiff = currDiff;
      


/* days[bestPrevDate] is the best previous date, 
   days[bestNextDate] is the best next date */

【讨论】:

我可以将 Date 对象添加到数组中,但 Dates 必须是子对象,因为每个 day-object 还包含其他(与此问题无关)信息。您的方法是否还能够搜索数组中对象的属性并返回包含(最匹配)属性的对象? 另外,找到最接近的匹配 beforeafter 目标日期也很重要,所以我必须始终以两个日期结束, 除非目标日期在数组中的第一天之前或最后一天之后。 @ReinoudSchuijers:添加了最接近的匹配。如果您的对象包含其他信息并且Date 是一个特定属性(比如说days[i].the_date_object,那么我的方法能够做到这一点。 @ReinoudSchuijers:上面提供的方法不需要排序数组。但是,这些结果如何“奇怪”?你能提供一个你的代码和数据的例子吗? @ReinoudSchuijers:愚蠢的我,我不小心使用了错误的比较。 bestNextDiffbestPrevDiff 的起始值也是错误的。【参考方案2】:

您可以轻松地将sort function 与自定义比较器功能一起使用:

// assuming you have an array of Date objects - everything else is crap:
var arr = [new Date(2012, 7, 1), new Date(2012, 7, 4), new Date(2012, 7, 5), new Date(2013, 2, 20)];
var diffdate = new Date(2012, 7, 11);

arr.sort(function(a, b) 
    var distancea = Math.abs(diffdate - a);
    var distanceb = Math.abs(diffdate - b);
    return distancea - distanceb; // sort a before b when the distance is smaller
);

// result:
[2012-08-05, 2012-08-04, 2012-08-01, 2013-03-20]

要仅获得diffdate 之前或之后的结果,您可以filter 该数组:

var beforedates = arr.filter(function(d) 
    return d - diffdate < 0;
),
    afterdates = arr.filter(function(d) 
    return d - diffdate > 0;
);

如果您的自定义数组包含 the_date_object: new Date(...) 对象,则需要调整排序算法

    var distancea = Math.abs(diffdate - a.the_date_object);
    var distanceb = Math.abs(diffdate - b.the_date_object);

【讨论】:

+1:sort() 的用法很好。但是,这不会导致 O(N log N) 运行时间吗? 当然,这取决于您是想按顺序获取所有日期还是只需要一个选择算法。 我喜欢这个,filter() 方法我没有想到。也可以很好地翻译成underscore 等人。【参考方案3】:

Zeta's 的答案非常好,但是如果您想知道任一方向上最近的 N 个对象,我对您如何处理这个问题很感兴趣。这是我的刺:

var objects = [
     day_year: "2012",
      day_month: "08",
      day_number: "02"
    ,
     day_year: "2012",
      day_month: "08",
      day_number: "04"
    ,
     day_year: "2012",
      day_month: "08",
      day_number: "23"
    
];

var testDate = new Date('08/11/2012'),
    nextDateIndexesByDiff = [],
    prevDateIndexesByDiff = [];

for(var i = 0; i < objects.length; i++) 
    var thisDateStr = [objects[i].day_month, objects[i].day_number, objects[i].day_year].join('/'),
        thisDate    = new Date(thisDateStr),
        curDiff     = testDate - thisDate;

    curDiff < 0
        ? nextDateIndexesByDiff.push([i, curDiff])
        : prevDateIndexesByDiff.push([i, curDiff]);


nextDateIndexesByDiff.sort(function(a, b)  return a[1] < b[1]; );
prevDateIndexesByDiff.sort(function(a, b)  return a[1] > b[1]; );

console.log(['closest future date', objects[nextDateIndexesByDiff[0][0]]]);
console.log(['closest past date', objects[prevDateIndexesByDiff[0][0]]]);

【讨论】:

出于某种原因,这对我有用,而 Zeta 的回答却没有。 edit:在你改变它之前它已经工作了。现在我在...? nextDateIndexesByDiff.push([i, curDiff]); : prevDateIndexesByDiff... 收到一个错误(意外令牌;) 抱歉,我在将 if/else 切换为三元时添加了一个语法错误。 没关系。对于未来的日期,可以通过以下方式改进日志:if(nextDateIndexesByDiff.length &lt; 1) console.log("No future dates"); else console.log(['closest future date', objects[nextDateIndexesByDiff[0][0]]]); if(prevDateIndexesByDiff.length &lt; 1) console.log("No dates in the past"); else console.log(['closest past date', objects[prevDateIndexesByDiff[0][0]]]); ,否则您会收到错误消息。 这个方法好像还是有问题。每当日期数组长于 10 时,就会出错。我使用以下内容生成数组:for(var i = 0; i &lt; 11; i++) var d = new Date(); d.setDate(d.getDate() + i); full_day_array.push(day_year: d.getFullYear().toString(), day_month: (d.getMonth() + 1).toString(), day_number: d.getDate().toString()); 查看我对这个问题的评论。【参考方案4】:

无论日期数组有多长,这都有效:

function newFindClosest(dates, testDate) 
    var before = [];
    var after = [];
    var max = dates.length;
    for(var i = 0; i < max; i++) 
        var tar = dates[i];
        var arrDate = new Date(tar.day_year, tar.day_month, tar.day_number);
        // 3600 * 24 * 1000 = calculating milliseconds to days, for clarity.
        var diff = (arrDate - testDate) / (3600 * 24 * 1000);
        if(diff > 0) 
            before.push(diff: diff, index: i);
         else 
            after.push(diff: diff, index: i);
        
    
    before.sort(function(a, b) 
        if(a.diff < b.diff) 
            return -1;
        
        if(a.diff > b.diff) 
            return 1;
        
        return 0;
    );

    after.sort(function(a, b) 
        if(a.diff > b.diff) 
            return -1;
        
        if(a.diff < b.diff) 
            return 1;
        
        return 0;
    );
    return datesBefore: before, datesAfter: after;

【讨论】:

【参考方案5】:

这是我们使用的:

此函数在 array 中查找与 dateToCompare 具有最接近日期(名为 dateParam)的项目。

对于每个 item[dateParam],返回数组中与 dateToCompare 日期最近的元素

getClosestDateInArray (array, dateParam, dateToCompare) 
  let minDiff = null;
  let mostAccurateDate = array[0];
  array.map((item) => 
    const diff = Math.abs(moment(dateToCompare).diff(item[dateParam], 'minutes', true));
    if (!minDiff || diff < minDiff) 
      minDiff = diff;
      mostAccurateDate = item
    
  );
  return mostAccurateDate;

此解决方案需要 momentJS 库

【讨论】:

【参考方案6】:

你可以这样试试

var dates = [
'July 16, 1995 03:24:00',
'Aug 18, 1995 03:24:00',
'August 19, 1995 03:24:00',
'September 17, 1995 03:24:00',
'September 14, 1995 03:24:00',
'August 18, 1995 03:24:00',
'July 16, 1995 03:24:00',
'December 15, 1995 03:24:00',
'July 13, 1995 03:24:00',
]

var temp = dates.map(d => Math.abs(new Date() - new Date(d).getTime()));
var idx = temp.indexOf(Math.min(...temp));
console.log(dates[idx]);

【讨论】:

以上是关于使用 JavaScript 在数组中查找最近的日期的主要内容,如果未能解决你的问题,请参考以下文章

用于在两个数组中查找公共元素的 Javascript 程序

查找个人最近发生的事件

使用LINQ在数组中查找最小和最大日期?

SQL 中高效的“查找最近的数字或日期”,其中日期/数字列被索引覆盖

在用户日表中查找最近的购买日期

使用 Java 8 流在 LocalDate 属性的对象列表中查找最近的日期