【问题标题】:Unique Array for dates Javascript日期Javascript的唯一数组
【发布时间】:2017-03-13 19:22:30
【问题描述】:

我看到这个问题经常被问到常规的 javascript 数组,但是如果它是一个日期数组,似乎没有一个答案有效。

我可以通过试错来解决这个问题,但如果我问的话,我确实看到了对其他人的一些好处。

基本上,如果您有一个可能有重复的日期的 javascript 数组,并且需要过滤到一个没有重复的数组中,那么最好的方法是什么?

我尝试了Array.from(new Set(arr))的ES6解决方案,但它只是返回相同的数组。

我也试过了

Array.prototype.unique = function() {
    var a = [];
    for (var i=0, l=this.length; i<l; i++)
        if (a.indexOf(this[i]) === -1)
            a.push(this[i]);
    return a;
}

都来自Unique values in an array

但是两者都不起作用,看起来indexOf 不适用于日期对象。

这是我的数组是如何在 atm 生成的

//this is an array generated from ajax data, 
//its a year over year comparison with a separate year, 
//so to create a reliable date objects I force it to use the same year.
data.map(d => {
   dp = new Date(Date.parse(d.date + '-' + d.year));
   dp.setFullYear(2000);
   return dp;
})

大约有 100 天左右不同,但最终总是有大约 350 个索引。

【问题讨论】:

  • 这可能会有所帮助:stackoverflow.com/questions/492994/…
  • 将每个日期放在一个集合中。
  • 绝对是正确的方向,只需要找出一种使用它来过滤数组的方法,宁愿在不创建其他变量的情况下内联,无论如何我都会尝试这个想法,谢谢@mparnisari
  • 如果我理解正确你想要一组数据? developer.mozilla.org/en/docs/Web/JavaScript/Reference/… "Set 对象是值的集合,可以按插入顺序迭代其元素。Set 中的值只能出现一次;它在 Set 的集合中是唯一的。"
  • @JordanRamstad .map 将所有 Date 对象序列化为字符串 -> 将它们添加到 Set -> 从 Set 创建新数组 -> map 到 Date 对象中。或者通过维护所有序列化日期的查找来简单地filter。可能在一个集合中。所以过滤函数将类似于if (Date_as_string in my_lookup) { discard } else { add it to the set and return true}

标签: javascript arrays date


【解决方案1】:

日期的问题在于运算符 ===!== 不能按预期工作(即它们比较指针而不是实际值)。

一种解决方案是使用Underscore's uniq 函数和自定义转换函数来比较值:

var dates = data.map(d => {
   dp = new Date(Date.parse(d.date + '-' + d.year));
   dp.setFullYear(2000);
   return dp;
})

var unique = _.uniq(dates, false, function (date) {
   return date.getTime();
})

【讨论】:

  • 我宁愿避免添加 underscore.js,因为我的应用程序中的其他任何地方都不需要它,所以我没有测试这个答案。但是,如果它确实有效,我会说这是一个相当干净的替代方案,如果已经使用 underscore.js
【解决方案2】:

如果通过=== 比较两个日期,则比较两个日期对象的引用。表示相同日期的两个对象仍然是不同的对象。

相反,比较来自Date.prototype.getTime() 的时间戳:

function isDateInArray(needle, haystack) {
  for (var i = 0; i < haystack.length; i++) {
    if (needle.getTime() === haystack[i].getTime()) {
      return true;
    }
  }
  return false;
}

var dates = [
  new Date('October 1, 2016 12:00:00 GMT+0000'),
  new Date('October 2, 2016 12:00:00 GMT+0000'),
  new Date('October 3, 2016 12:00:00 GMT+0000'),
  new Date('October 2, 2016 12:00:00 GMT+0000')
];

var uniqueDates = [];
for (var i = 0; i < dates.length; i++) {
  if (!isDateInArray(dates[i], uniqueDates)) {
    uniqueDates.push(dates[i]);
  }
}

console.log(uniqueDates);

优化和错误处理由您决定。

【讨论】:

  • 这行得通,我的数据集的性能相当不错,大约为 0.368 毫秒。
  • 请不要这样做,除非您只有少量日期,如示例中所示。时间复杂度是二次的。如果您只使用存储getTime() 毫秒数的Set 数字,则可以使用摊销常数的has 操作,使完整的唯一操作线性化。
【解决方案3】:

您可以使用查找进行简单的过滤,但您需要将日期转换为可以比较的日期,因为在 JavaScript 中两个对象永远不会相同,除非它是对完全相同对象的两个引用。

const dates = [
  new Date(2016, 09, 30, 10, 35, 40, 0),
  new Date(2016, 09, 30, 10, 35, 40, 0), //same
  new Date(2016, 09, 30, 10, 35, 40, 0), //same
  new Date(1995, 07, 15, 03, 15, 05, 0) //different
];


function filterUniqueDates(data) {
  const lookup = new Set();
  
  return data.filter(date => {
     const serialised = date.getTime();
    if (lookup.has(serialised)) {
      return false;
    } else { 
      lookup.add(serialised);
      return true;
    }
  })
}

console.log(filterUniqueDates(dates));

这可以进一步概括,如果你想通过改变你确定唯一性的方式来过滤任何东西

const dates = [
  new Date(2016, 09, 30, 10, 35, 40, 0),
  new Date(2016, 09, 30, 10, 35, 40, 0), //same
  new Date(2016, 09, 30, 10, 35, 40, 0), //same
  new Date(1995, 07, 15, 03, 15, 05, 0) //different
];

const dateSerialisation = date => date.getTime(); // this is the previous logic for dates, but extracted

//as primitives, these can be compared for uniqueness without anything extra
const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
const strings = ["a", "b", "b", "c", "c", "c"];

const people = [
  {name: "Alice", age: 20},
  {name: "Bob", age: 30},
  {name: "Bob", age: 40}, //technically the same
  {name: "Carol", age: 50},
  {name: "Carol", age: 60}, //technically the same
  {name: "Carol", age: 70} //technically the same
]

//let's assume that a person with the same name is the same person regardless of anything else 
const peopleSerialisation = person => person.name;

/* 
 * this now accepts a transformation function that will be used 
 * to find duplicates. The default is an identity function that simply returns the same item.
 */
function filterUnique(data, canonicalize = x => x) { 
  const lookup = new Set();
  
  return data.filter(item => {
     const serialised = canonicalize(item); //use extract the value by which items are considered unique
    
    if (lookup.has(serialised)) {
      return false;
    } else { 
      lookup.add(serialised);
      return true;
    }
  })
}


console.log("dates", filterUnique(dates, dateSerialisation));
console.log("numbers", filterUnique(numbers));
console.log("strings", filterUnique(strings));
console.log("people", filterUnique(people, peopleSerialisation));

这是使用 ES6,但转换为符合 ES5 的代码很简单 - 删除粗箭头函数、默认参数和 new Set() 这是您需要的:

function filterUnique(data, canonicalize) {
  if (!canonicalize) {
    canonicalize = function(x) { return x; }
  }

  var lookup = {};

  return data.filter(function(item) {
     var serialised = canonicalize(item);

    if (lookup.hasOwnProperty(serialised)) {
      return false;
    } else { 
      lookup[serialised] = true;
      return true;
    }
  })
}

【讨论】:

  • 这个答案有效并且看起来相当干净。然而,TimoSta 的效果也很好,而且也相当干净。所以我测量了时间,TimoSta 在大约 0.368ms 的性能比这个脚本在相同数据上需要大约 2.97ms 的性能更高。无论哪种方式都不长,但认为这将是一个很好的检查方式。从长远来看,时差还不错,所以虽然我认为 TimoSta 是最好的,但这绝对是一个很好的资源。
  • 是的,我想有办法让它更快。这里只是说明这个概念。我自己肯定会采用更实用的方法,主要是因为.filter 已经存在,所以我不需要编写单独的循环和其他东西。
【解决方案4】:

ES6方式:

datesArray.filter((date, i, self) => 
  self.findIndex(d => d.getTime() === date.getTime()) === i
)

感谢https://stackoverflow.com/a/36744732/3161291

【讨论】:

    【解决方案5】:

    你可以使用Array.prototype.reduce():

    const dates = [
      new Date(2016, 09, 30, 10, 35, 40, 0), 
      new Date(2016, 09, 30, 10, 35, 40, 0), // same
      new Date(2016, 09, 30, 10, 35, 40, 0), // same
      new Date(1995, 07, 15, 03, 15, 05, 0)  // different
    ];
    
    const uniqueDates = dates.reduce((a, c) => {
      !a.hash[c] && a.result.push(c);
      a.hash[c] = true;
      return a;
    }, {result: [], hash: {}});
    
    console.log(uniqueDates.result);

    【讨论】:

      【解决方案6】:

      根据其他一些答案,我这样做了(ES6+):

      const uniqueDates = [...new Set(dateInputArray.map(r => r.getTime()))].map((r: number)=>(new Date(r)));
      

      使用 getTime() 创建一组唯一转换为数字的日期,然后将它们映射回 Date 对象数组。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-27
        • 1970-01-01
        • 1970-01-01
        • 2020-01-21
        • 2016-02-14
        • 1970-01-01
        • 2022-09-28
        相关资源
        最近更新 更多