按属性创建唯一对象数组

IT技术 javascript jquery filter
2021-02-09 01:49:26

我创建了一个对象数组,如下所示:

[
    {
        "lat": 12.123,
        "lng": 13.213,
        "city": "New York"
    },
    {
        "lat": 3.123,
        "lng": 2.213,
        "city": "New York"
    },
    {
        "lat": 1.513,
        "lng": 1.113,
        "city": "London"
    }
]

我正在尝试创建一个新数组,该数组将过滤器过滤places为仅包含不具有相同city属性的对象(lat/lng 重复项可以)。是否有内置的 JS 或 Jquery 函数来实现这一点?

6个回答

我可能会在过滤过程中使用标志对象,如下所示:

var flags = {};
var newPlaces = places.filter(function(entry) {
    if (flags[entry.city]) {
        return false;
    }
    flags[entry.city] = true;
    return true;
});

这使用Array#filter了 ECMAScript5 (ES5),它是 ES5 中可以填充的添加项之一(搜索“es5 shim”以获得多个选项)。

你可以不用filter,当然,它只是更冗长一点:

var flags = {};
var newPlaces = [];
var index;
for (index = 0; index < places.length; ++index) {
    if (!flags[entry.city]) {
        flags[entry.city] = true;
        newPlaces.push(entry);
    }
});

以上都假设应该保留给定城市第一个对象,而其他所有对象都应丢弃。


注意:正如 user2736012 在下面指出的那样,我的测试if (flags[entry.city])对于名称恰好Object.prototypetoString. 在这种情况下不太可能,但有四种方法可以避免这种可能性:

  • (我通常首选的解决方案)创建没有原型的对象:var flags = Object.create(null);. 这是 ES5 的一个特性。请注意,对于像 IE8 这样过时的浏览器,这不能被填充(除非该参数的值为 ,否则的单参数版本Object.create可以)。null

  • 使用hasOwnProperty的测试,例如,if (flags.hasOwnProperty(entry.city))

  • 在您知道任何Object.prototype属性不存在的前缀上添加前缀,例如xx

    var key = "xx" + entry.city;
    if (flags[key]) {
        // ...
    }
    flags[key] = true;
    
  • 从 ES2015 开始,您可以使用 aSet代替:

    const flags = new Set();
    const newPlaces = places.filter(entry => {
        if (flags.has(entry.city)) {
            return false;
        }
        flags.add(entry.city);
        return true;
    });
    
使用 Javascript 中的新 Set 类,您可以用新的 Set() 替换标志对象,并使用 flags.has(key) 和 flags.add(key)。那么您就不必担心与对象原型属性发生冲突。
2021-03-20 01:49:26
@undefined:是的,但我认为用户的观点是,如果你概括这一点,这是可能的。例如,如果有人概括了该技术并被该constructor财产所困扰,我不会感到惊讶他/她提出了一个非常有用的观点。
2021-03-22 01:49:26
是的,这有点偏执。我承认,当然对于这种情况。不过,它确实使它成为更安全的通用解决方案。
2021-03-24 01:49:26
最佳答案:几乎所有其他解决方案都在 O(n^2) 时间或类似的时间内运行。@Robert Byrne 的解决方案是 O(2*n) 次之。这几乎只是 O(n)。
2021-03-24 01:49:26
虽然极不可能有一个名为“toString”的城市或任何其他Object.prototype属性,但使用它仍然不是一个坏主意.hasOwnProperty()
2021-03-27 01:49:26

es6 的最短但不是最佳性能(请参阅下面的更新)解决方案:

function unique(array, propertyName) {
   return array.filter((e, i) => array.findIndex(a => a[propertyName] === e[propertyName]) === i);
}

性能:https : //jsperf.com/compare-unique-array-by-property

@MANaseer 抱歉,现在知道为什么删除了链接,无法恢复。
2021-03-22 01:49:26
你真是个天才!
2021-03-27 01:49:26
此解决方案具有额外的好处,即它可以与具有某种非平凡equals功能的对象一起使用在这种情况下,findIndex调用的内部变为a => a.equals(e)
2021-03-28 01:49:26
2018年 8 月 19 日更新:在 Chrome 67.0.3396 / Mac OS X 10.13.6 中获得了此处建议的 2 个其他变体的最佳性能分数。使用前检查目标浏览器的性能。
2021-03-31 01:49:26

https://lodash.com/docs#uniqBy

https://github.com/lodash/lodash/blob/4.13.1/lodash.js#L7711

/**
 * This method is like `_.uniq` except that it accepts `iteratee` which is
 * invoked for each element in `array` to generate the criterion by which
 * uniqueness is computed. The iteratee is invoked with one argument: (value).
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Array
 * @param {Array} array The array to inspect.
 * @param {Array|Function|Object|string} [iteratee=_.identity]
 *  The iteratee invoked per element.
 * @returns {Array} Returns the new duplicate free array.
 * @example
 *
 * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
 * // => [2.1, 1.2]
 *
 * // The `_.property` iteratee shorthand.
 * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
 * // => [{ 'x': 1 }, { 'x': 2 }]
 */

我对@IgorL 解决方案进行了一些扩展,但扩展了原型并为其提供了一个选择器函数而不是一个属性,以使其更加灵活:

Array.prototype.unique = function(selector) {
   return this.filter((e, i) => this.findIndex((a) => {
      if (selector) {
        return selector(a) === selector(e);
      }
      return a === e;
    }) === i);
};

用法:

// with no param it uses strict equals (===) against the object
let primArr = ['one','one','two','three','one']
primArr.unique() // ['one','two','three']

let a = {foo:123}
let b = {foo:123}
let fooArr = [a,a,b]
fooArr.unique() //[a,b]

// alternatively, you can pass a selector function
fooArr.unique(item=>item.foo) //[{foo:123}] (first "unique" item returned)

绝对不是执行此操作的最高效方法,但只要选择器简单且数组不是很大,它应该可以正常工作。

在typescript中

Array.prototype.unique = function<T>(this: T[], selector?: (item: T) => object): T[] {
   return this.filter((e, i) => this.findIndex((a) => {
      if (selector) {
        return selector(a) === selector(e);
      }
      return a === e;
    }) === i);
};
这是最好的解决方案!如果有人可以提供最高效的方式,那就太棒了。但是在这些情况下,对原型进行扩展是正确的方法,并且使用选择器也是正确的方法(我正在使用typescript)
2021-03-15 01:49:26
@Worthy7 我也在使用typescript。我在下面添加了打字版本
2021-03-22 01:49:26
谢谢你。在我的用例中完美运行!
2021-04-09 01:49:26

您可以filter使用 a Setby 仅包含具有尚未添加到 的属性值的元素Set(之后应将其添加到Set)。这可以使用逻辑和运算符 ( &&)在一行中完成

下面是一个通用函数,用于根据对象数组 ( prop) 中的特定属性 ( )获取唯一对象数组arr请注意,在重复的情况下,只会保留具有属性值的第一个对象。

const getUniqueBy = (arr, prop) => {
  const set = new Set;
  return arr.filter(o => !set.has(o[prop]) && set.add(o[prop]));
};

演示: