【问题标题】:Find last matching object in array of objects在对象数组中查找最后一个匹配的对象
【发布时间】:2015-10-21 20:42:37
【问题描述】:

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

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”的数组值。我正在寻找 fruits.shape = round 的位置。

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

但是仅仅使用它是行不通的。我确定我错过了一些简单的东西。如何在数组中找到对象形状 = 圆形的最后一项?

【问题讨论】:

  • 你是在寻找这个对象的索引还是只是对象本身?
  • 实际上,两者都可以。如果我有索引,我可以访问该对象。

标签: javascript arrays


【解决方案1】:
var fruit = fruits.slice().reverse().find(fruit => fruit.shape === currentShape);

【讨论】:

  • 对于那些想知道为什么需要 slice() 的人:reverse() 正在变异! slice() 给你一个副本来处理。
  • 可以像const fruit = [...fruits].reverse().find(fruit => fruit.shape === currentShape); 一样完成
  • 可能看起来比公认的答案更简洁,但复制和反转数组并不是查找元素的有效方法。
  • @Pawel 取决于数组的大小和执行操作的频率。在微观层面上赢得性能通常是对 imo 可读性和可维护性的良好权衡
  • 可读性总是相对的(因为它是主观的)。对我来说,与 for 循环相比,这个解决方案需要更少的认知负担来解析。我并不是说 for 循环很难阅读,但是没有编码背景的人可以理解这个解决方案,因为它几乎读起来像英语或伪代码。在过去的几年里,我开始更加重视代码的阅读体验,并且更喜欢这种解决方案而不是 for 循环(即使后者性能更高)。只是我在这件事上的 2 美分。
【解决方案2】:

您可以将数组转换为数组boolean 类型并获取最后一个true 索引。

const lastIndex = fruits.map(fruit => 
  fruit.shape === currentShape).lastIndexOf(true);

【讨论】:

  • 超级优雅的解决方案
  • 非常巧妙的方法!
  • 适用于小数组和简单条件,否则效率很低,因为它会处理所有数组
  • 太棒了!非常适合我的需求。
【解决方案3】:
var previousInShapeType, index = fruits.length - 1;
for ( ; index >= 0; index--) {
    if (fruits[index].shape == currentShape) {
        previousInShapeType = fruits[index];
        break;
    }
}

你也可以通过数组向后循环。

小提琴:http://jsfiddle.net/vonn9xhm/

【讨论】:

  • 这是这里唯一正确的答案。人们不再考虑效率了吗?
  • 同意,我会说函数式编程已经破坏了效率
  • 这是最快的解决方案,运行速度为 55,323,764 ops/s,而以下解决方案的平均速度为 2,200 ops/s(快 25,000 倍)-(在最新的 google chrome 上收集 100k 个水果进行测试)。但是!如果我们不关心一次反向收集的成本并且我们缓存反向副本,那么 .reverse().find 解决方案实际上在 55,853,952 ops/s 时执行速度更快
  • 此外,在大多数情况下,除非您绝对需要,否则代码应该针对可维护性而非效率进行优化。如果你知道你会有一个简短的列表,请使用可读的方法。
  • 过早的优化会破坏可维护性,进而会破坏真正重要的优化。不要为了微不足道的优化而牺牲可维护性。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

    • 这实际上是纯javascript中最优雅的解决方案。我不知道为什么它被否决了
    • 它不应该用 0 初始化。也许是 -1,因为它是索引查找的分解标准。此外 - 生成的 const 不是水果对象本身,而是索引。
    • @avioli,感谢您的这两点。我用这些更改编辑了答案。
    【解决方案6】:

    一个更简单且相对有效的解决方案。 过滤并弹出!

    过滤所有匹配当前形状的水果,然后pop得到最后一个。

    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);

    【讨论】:

      【解决方案7】:

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

      Proposal for Array.prototype.findLastArray.prototype.findLastIndex 现在处于第 3 阶段!

      以下是您可以使用它们的方法:

      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
      

      您可以在this V8 blog post阅读更多内容。

      您可以在"New in Chrome" series找到更多信息。

      【讨论】:

      • 想知道如何用 TypeScript 教 VS Code 来识别这种方法的存在吗?
      • 我猜你应该更新到支持它的最新版本的 NodeJS。
      • 我正在为浏览器编写代码。而当前的 Chrome (96) 不支持它。大概,我应该等半年左右
      • 也在这个 Youtube 视频上:youtube.com/…
      【解决方案8】:

      纯JS:

      var len = fruits.length, prev = false;
      while(!prev && len--){
          (fruits[len].shape == currentShape) && (prev = fruits[len]);
      }
      

      lodash:

      _.findLast(fruits, 'shape', currentShape);
      

      【讨论】:

        【解决方案9】:

        虽然当前接受的答案可以解决问题,但 ES6 (ECMA2015) 的到来添加了 spread 运算符,这使得复制您的数组变得容易(这对于您的示例中的 fruit 数组可以正常工作,但要注意嵌套数组)。您还可以利用 pop 方法返回已删除元素的事实来使您的代码更简洁。因此,您可以使用以下 2 行代码实现所需的结果

        const currentShape = fruits.pop().shape;
        const previousInShapeType = [...fruits].reverse().find(
          fruit => fruit.shape === currentShape
        );
        

        【讨论】:

          【解决方案10】:

          基于Luke Liu's answer,但使用ES6的spread operator使其更易于阅读:

          const fruit = [...fruits].reverse().find(fruit => fruit.shape === currentShape);
          

          【讨论】:

            【解决方案11】:

            更新 - 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);

            **在linklink使用前请检查浏览器是否兼容

            阅读更多关于findLasthere

            实现此目的的另一种方法是使用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);

            【讨论】:

              【解决方案12】:

              我会建议另一个不错的解决方案,它不需要使用reverse() 克隆新对象。

              我使用reduceRight 来代替。

              function findLastIndex(array, fn) {
                if (!array) return -1;
                if (!fn || typeof fn !== "function") throw `${fn} is not a function`;
                return array.reduceRight((prev, currentValue, currentIndex) => {
                  if (prev > -1) return prev;
                  if (fn(currentValue, currentIndex)) return currentIndex;
                  return -1;
                }, -1);
              }
              

              及用法

              findLastIndex([1,2,3,4,5,6,7,5,4,2,1], (current, index) => current === 2); // return 9
              
              findLastIndex([{id: 1},{id: 2},{id: 1}], (current, index) => current.id === 1); //return 2
              
              

              【讨论】:

                【解决方案13】:

                你应该使用过滤器! filter 接受一个函数作为参数,并返回一个新数组。

                var roundFruits = fruits.filter(function(d) {
                 // d is each element of the original array
                 return d.shape == "round";
                });
                

                现在 roundFruits 将包含函数返回 true 的原始数组的元素。现在,如果您想知道原始数组索引,请不要害怕 - 您可以使用函数映射。 map 还对数组进行操作,并采用作用于数组的函数。我们可以将 map 和 filter 链接在一起,如下所示

                var roundFruits = fruits.map(function(d, i) {
                  // d is each element, i is the index
                  d.i = i;  // create index variable
                  return d;
                }).filter(function(d) {
                  return d.shape == "round"
                });
                

                结果数组将包含原始 fruits 数组中形状为圆形的所有对象,以及它们在 fruits 数组中的原始索引。

                roundFruits = [
                { 
                    shape: round,
                    name: orange,
                    i: 0
                },
                { 
                    shape: round,
                    name: apple,
                    i: 1
                },
                { 
                    shape: round,
                    name: grapefruit
                    i: 4
                }
                ]
                

                现在,您可以在准确了解相关数据位置的情况下做任何您需要的事情。

                // get last round element
                fruits[4];
                

                【讨论】:

                • 如果我不关心使用一些额外的内存等,我倾向于使用filter()pop(),比如arr.filter(({shape) => shape === 'round').pop()
                【解决方案14】:

                这是一个打字稿版本:

                /**
                 * Returns the value of the last element in the array where predicate is true, and undefined
                 * otherwise. It's similar to the native find method, but searches in descending order.
                 * @param list the array to search in.
                 * @param predicate find calls predicate once for each element of the array, in descending
                 * order, until it finds one where predicate returns true. If such an element is found, find
                 * immediately returns that element value. Otherwise, find returns undefined.
                 */
                export function findLast<T>(
                  list: Array<T>,
                  predicate: (value: T, index: number, obj: T[]) => unknown
                ): T | undefined {
                  for (let index = list.length - 1; index >= 0; index--) {
                    let currentValue = list[index];
                    let predicateResult = predicate(currentValue, index, list);
                    if (predicateResult) {
                      return currentValue;
                    }
                  }
                  return undefined;
                }
                

                用法:

                const r = findLast([12, 43, 5436, 44, 4], v => v < 45);
                console.log(r); // 4
                

                【讨论】:

                  猜你喜欢
                  • 2013-09-16
                  • 2019-11-02
                  • 2019-05-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-07-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多