在对象数组中查找最后一个匹配的对象

74

我有一个对象数组。我需要获取最后一个对象的类型(在此示例中为“形状”),将其删除,然后找到先前在数组中具有相同类型(例如“形状”)的对象的索引。

var fruits = [
    { 
        shape: round,
        name: orange
    },
    { 
        shape: round,
        name: apple
    },
    { 
        shape: oblong,
        name: zucchini
    },
    { 
        shape: oblong,
        name: banana
    },
    { 
        shape: round,
        name: grapefruit
    }
]

// What's the shape of the last fruit
var currentShape =  fruits[fruits.length-1].shape;

// Remove last fruit
fruits.pop(); // grapefruit removed

// Find the index of the last round fruit
var previousInShapeType = fruits.lastIndexOf(currentShape);
    // should find apple, index = 1

所以,显然这个例子中的类型将是“round”。但我不是在寻找一个数组值为“round”的结果。我要找的是水果形状 = 圆形的地方。

var previousInShapeType = fruits.lastIndexOf(fruits.shape = currentShape);

但仅仅使用那个并不起作用。我确定我错过了一些简单的东西。如何找到数组中最后一个形状为“round”的对象?


你是在寻找这个对象的索引还是仅仅是对象本身? - Richard Hamilton
实际上,两种方法都可以。如果我有索引,我就可以访问对象。 - Graeck
15个回答

137
var fruit = fruits.slice().reverse().find(fruit => fruit.shape === currentShape);

或更新的ECMAscript规范提供了不可变方法Array.prototype.toReversed()

const fruit = fruits.toReversed().find(fruit => fruit.shape === currentShape);

62
对于那些想知道为什么需要使用 slice() 的人:reverse() 是有副作用的!slice() 可以给你一个可操作的复制品。 - Michael Rush
7
可以这样实现:const fruit = [...fruits].reverse().find(fruit => fruit.shape === currentShape); - Joe Lloyd
4
这个答案或许看起来比被接受的答案更简洁,但复制并反转一个数组不是寻找元素的高效方式。 - Pawel
5
根据数组的大小和操作的频率,性能的提升通常可以作为可读性和可维护性的一种平衡。在我看来,微观层面上的性能提升通常是值得的。 - kano
2
可读性总是相对的(因为它是主观的)。对我来说,与for循环相比,这个解决方案需要更少的认知负荷来解析。我并不是说for循环很难阅读,但是这个解决方案可以被没有编码背景的人理解,因为它几乎像英语或伪代码一样阅读。在过去的几年中,我已经开始重视代码的阅读体验,并且更喜欢这个解决方案而不是for循环(即使后者更高效)。这只是我的个人看法。 - kano
显示剩余2条评论

66
你可以将你的数组转换为布尔类型数组,并获取最后一个“真”的索引。
const lastIndex = fruits.map(fruit => 
  fruit.shape === currentShape).lastIndexOf(true);

2
超级优雅的解决方案 - interwebjill
1
真的很棒的方法! - Jay
7
适用于小数组和简单条件,但在处理大数组和复杂条件时非常低效,因为它会处理整个数组。 - bviala
太棒了!!完美地满足了我的需求。 - Fateh Khalsa

26
var previousInShapeType, index = fruits.length - 1;
for ( ; index >= 0; index--) {
    if (fruits[index].shape == currentShape) {
        previousInShapeType = fruits[index];
        break;
    }
}

你也可以反向循环数组。

示例代码:http://jsfiddle.net/vonn9xhm/


13
这是这里唯一正确的答案。人们不再注重效率了吗? - Artless
2
我认为“函数式编程破坏了效率”这种说法是不正确的。 - EugenSunic
3
这是最快的解决方案,运行速度为每秒55,323,764次操作,而下面的解决方案平均只有每秒2,200次操作(慢了25,000倍)-(在最新的Google Chrome上测试了100,000个水果)。但是!如果我们不介意反转集合的成本,并缓存反转副本,那么.reverse().find解决方案实际上更快,每秒可执行55,853,952次操作。 - Adassko
2
此外,在大多数情况下,代码应该优化为易维护而非高效,除非您确实需要它。如果您知道您将拥有一个短列表,请使用可读性方法。 - Marcel
3
过早地进行优化可能会破坏可维护性,反过来又会破坏真正重要的优化。不要为了微不足道的优化牺牲可维护性。 - JakeDK
3
@Artless非常高效,他把“for”写成了“to”,把“efficiency”写成了“efficient”,省下了两个字符! - Arik

22

使用 Lodash 库,您可以找到最后一个逻辑元素。

_.findLast([1,2,3,5,4], n => n % 2 == 1); // Find last odd element
// expected output: 5

17

更新-2021年10月27日(Chrome 97+)

关于Array.prototype.findLastArray.prototype.findLastIndex的提案现在已经进入了阶段34!

以下是如何使用它们:

const fruits = [
  { shape: 'round', name: 'orange' },
  { shape: 'round', name: 'apple' },
  { shape: 'oblong', name: 'zucchini' },
  { shape: 'oblong', name: 'banana' },
  { shape: 'round', name: 'grapefruit' }
]

let last_element = fruits.findLast((item) => item.shape === 'oblong');
// → { shape: oblong, name: banana }

let last_element_index = fruits.findLastIndex((item) => item.shape === 'oblong');
// → 3

您可以在这篇 V8 博客文章中了解更多。

您可以在"新版 Chrome "系列视频中发现更多信息。


1
想知道如何教VS Code与TypeScript一起识别这种方法吗? - oluckyman
我猜你应该更新到支持它的最新版本的NodeJS。 - NeNaD
我正在为浏览器编写代码。而当前的Chrome(96)不支持它。也许,我应该等半年左右。 - oluckyman
Node.js 18和Chromium 101使用V8引擎版本10.1,支持这些功能。 - Константин Ван
显示剩余2条评论

13
一个更简单而且相对高效的解决方案是——筛选并弹出!
筛选匹配当前形状的所有水果,然后弹出最后一个。 fruits.filter(({shape}) => shape === currentShape).pop()

var fruits = [{
    shape: 'round',
    name: 'orange'
}, {
    shape: 'round',
    name: 'apple'
}, {
    shape: 'oblong',
    name: 'zucchini'
}, {
    shape: 'oblong',
    name: 'banana'
}, {
    shape: 'round',
    name: 'grapefruit'
}];

// What's the shape of the last fruit
var currentShape = fruits[fruits.length - 1].shape;

// Remove last fruit
fruits.pop(); // grapefruit removed


alert(fruits.filter(({shape}) => shape === currentShape).pop().name);


11

这是一种不依赖于reverse的解决方案,因此不需要对原始集合进行“克隆”。

const lastShapeIndex = fruits.reduce((acc, fruit, index) => (
    fruit.shape === currentShape ? index : acc
), -1);

这实际上是纯JavaScript中最优雅的解决方案。我不知道为什么它被踩了。 - Adassko
它不应该初始化为0。也许是-1,因为这是索引查找的一种事实标准。此外,“const”的结果不是果对象本身,而是索引。 - avioli
2
@avioli,感谢您提出的两点建议。我已经根据您的意见编辑了答案。 - Ed I

4

更新 - 现在可以使用Array.prototype.findLast()方法了

var fruits = [
    { 
        shape: 'round',
        name: 'orange'
    },
    { 
        shape: 'round',
        name: 'apple'
    },
    { 
        shape: 'oblong',
        name: 'zucchini'
    },
    { 
        shape: 'oblong',
        name: 'banana'
    },
    { 
        shape: 'round',
        name: 'grapefruit'
    }
]


const last = fruits.findLast(n => n.shape === 'oblong');
console.log(last);

在使用此链接之前,请务必检查浏览器兼容性。

了解更多关于findLast的信息,请在这里阅读。

另一种实现此功能的方法是使用reverse(但效率较低)。

var fruits = [
    { 
        shape: 'round',
        name: 'orange'
    },
    { 
        shape: 'round',
        name: 'apple'
    },
    { 
        shape: 'oblong',
        name: 'zucchini'
    },
    { 
        shape: 'oblong',
        name: 'banana'
    },
    { 
        shape: 'round',
        name: 'grapefruit'
    }
]


const last = fruits.reverse().find(n => n.shape === 'oblong');

console.log(last);


4

2
虽然目前被接受的答案可以解决问题,但ES6(ECMA2015)的到来增加了spread运算符,使得复制数组变得容易(这对于你示例中的fruit数组而言可以正常工作,但要注意嵌套数组)。你还可以利用pop方法返回被移除的元素的特性,使你的代码更简洁。因此,你可以通过以下两行代码实现所需的结果。
const currentShape = fruits.pop().shape;
const previousInShapeType = [...fruits].reverse().find(
  fruit => fruit.shape === currentShape
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接