61

Let's say I have an array of objects:

[
  { 'a': 'something',     'b':12 },
  { 'a': 'something',     'b':12 },
  { 'a': 'somethingElse', 'b':12 },
  { 'a': 'something',     'b':12 },
  { 'a': 'somethingElse', 'b':12 }
]

What would be the cleanest way to get the last index of an element where a has the value 'something' - in this case index 3? Is there any way to avoid loops?

pjpscriv
  • 866
  • 11
  • 20
Jacob
  • 3,580
  • 22
  • 82
  • 146

23 Answers23

77

Here's a reusable typescript version which mirrors the signature of the ES2015 findIndex function:

/**
* Returns the index of the last element in the array where predicate is true, and -1
* otherwise.
* @param array The source 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,
* findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
*/
export function findLastIndex<T>(array: Array<T>, predicate: (value: T, index: number, obj: T[]) => boolean): number {
    let l = array.length;
    while (l--) {
        if (predicate(array[l], l, array))
            return l;
    }
    return -1;
}
Nico Timmerman
  • 1,427
  • 12
  • 12
  • 16
    No copies, no reverse, no useless filter, O(n). Definitive. – Luca Davanzo Sep 08 '20 at 08:12
  • 6
    question are asking in js, so answer should be in js, even though ts and js are convertible. like: https://stackoverflow.com/a/33269005/5858238 – nyconing Sep 17 '20 at 06:57
  • You could have put all the type information in the JSDoc comment. Also, it doesn't mirror the signature - you're missing the `thisArg`. – RedGuy11 Sep 06 '21 at 17:03
43

You can use findIndex to get index. This will give you first index, so you will have to reverse the array.

var d = [{'a': "something", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}]

function findLastIndex(array, searchKey, searchValue) {
  var index = array.slice().reverse().findIndex(x => x[searchKey] === searchValue);
  var count = array.length - 1
  var finalIndex = index >= 0 ? count - index : index;
  console.log(finalIndex)
  return finalIndex;
}

findLastIndex(d, 'a', 'something')
findLastIndex(d, 'a', 'nothing')
Rajesh
  • 24,354
  • 5
  • 48
  • 79
17
let newArray = yourArray.filter((each)=>{
    return (each.a === something)
});
newArray[newArray.length-1];

You can also do

let reversedArray = yourArray.reverse();
reversedArray.find((each)=>{return each.a === something})
Amoolya S Kumar
  • 1,458
  • 9
  • 10
15

Reversing the array did not sound very straightforward to me, so my solution to my very similar case was using map() and lastIndexOf():

var lastIndex = elements.map(e => e.a).lastIndexOf('something');

Upd.: this answer from the dupe post makes it even better with map(cond).lastIndexOf(true)

Upd.: though this is not the most efficient solution, as we have to always traverse all array, whereas if we ran from end, we could end search the moment we found first match (@nico-timmerman's answer).

Klesun
  • 12,280
  • 5
  • 59
  • 52
13

You could iterate from the end and exit the loop if found.

var data = [{ a: 'something', b: 12 }, { a: 'something', b: 12 }, { a: 'somethingElse', b: 12 }, { a: 'something', b: 12 }, { a: 'somethingElse', b: 12 }],
    l = data.length;

while (l--) {
    if (data[l].a ==='something') {
        break;
    }
}

console.log(l);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
5

Update - Array.prototype.findLastIndex is now available for use -

var d = [{'a': "something", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}]

const lastIdx = d.findLastIndex(n => n.a === 'something');
console.log(lastIdx);

**Please check out this link to see what browsers are supporting findLastIndex before using it

You may also achieve this using reverse and findIndex but the performance is less better then using findLastIndex

var d = [{'a': "something", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}, {'a': "something", 'b':12}, {'a': "somethingElse", 'b':12}]

const lastIdx = d.reverse().findIndex(n => n.a === 'something');

console.log(lastIdx);

findLastIndex - https://github.com/tc39/proposal-array-find-from-last

findIndex - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

reverse - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse

Ran Turner
  • 14,906
  • 5
  • 47
  • 53
4

You could use Lodash:

import findLastIndex from "lodash/findLastIndex"

const data = [
  { a: 'something', b: 12 },
  { a: 'something', b: 12 },
  { a: 'somethingElse', b: 12 },
  { a: 'something', b: 12 },
  { a: 'somethingElse', b: 12 },
]

const lastIndex = findLastIndex(data, v => v.a === "something")
Sascha Klatt
  • 1,199
  • 1
  • 13
  • 23
3

First, this is not an array of objects but an array of arrays. An array of objects would look like this:

[{'a': something, 'b':12},
{'a': something, 'b':12},
{'a': somethingElse, 'b':12},
{'a': something, 'b':12},
{'a': somethingElse, 'b':12}]

Generally it's a good practice to use object syntax when you use non-numeric indices.

Second, to answer your question you can just use a reverse loop:

for(let i=(arr.length - 1); i>=0; i--){
    if(arr[i].a === "something"){
        index = i;
        break;
    }
}
dimlucas
  • 5,040
  • 7
  • 37
  • 54
3

What about something like this in ES6:

  arr.indexOf(arr.filter(item => item.a === 'something').pop())
Giorgio
  • 31
  • 1
3

This is what I used:

const lastIndex = (items.length -1) - (items.reverse().findIndex(el=> el.a === "something"))

Akram Rabie
  • 473
  • 5
  • 11
2

I wonder why lots of people would like to always avoid loops nowadays. They are just as natural structures as ifs and pure sequence of code lines. To avoid repeating yourself, write a function for it, what you even could add to Array.prototype. The following is a simple example, not tested, just for the idea.

For getting the index

Array.prototype.lastIndex = function(cond) {
  if (!this.length) return -1;
  if (!cond) return this.length-1;

  for (var i=this.length-1; i>=0; --i) {
    if (cond(this[i])) return i;
  }

  return -1;
}

Or for elements directly

Array.prototype.lastOrDefault = function(cond, defaultValue) {
  if (!this.length) return defaultValue;
  if (!cond) return this[this.length-1];

  for (var i=this.length-1; i>=0; --i) {
    if (cond(this[i])) return this[i];
  }

  return defaultValue;
}

Usage example:

myArr = [1,2,3,4,5];
var ind1 = myArr.lastIndex(function(e) { return e < 3; }); 
var num2 = myArr.lastOrDefault(function(e) { return e < 3; });
var num8 = myArr.lastOrDefault(function(e) { return e > 6; }, /* explicit default */ 8);
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93
  • Sorry in my first version I didn't take into account that the OP would like to get the index. I've added a version for that too. – Zoltán Tamási Dec 02 '16 at 10:13
  • Could you please tell me what does `(!cond)` mean? – MathCoder Jun 19 '20 at 00:06
  • @MathCoder `!cond` means that `cond` is *falsey*. In this particular case I used it to allow using the function without specifying any condition function, and in that case it returns the last index of the array or the last element (or the given default if array is empty). In generaly, it's just a precondition check, one could throw error as well, but I found that I can make up some semantics for that case too. – Zoltán Tamási Jun 19 '20 at 06:31
2

Please take a look at this method.

var array=[{a: 'something', b:12},
           {a: 'something', b:12},
           {a: 'somethingElse', b:12},
           {a: 'something', b:12},
           {a: 'somethingElse', b:12}
          ];

console.log(array.filter(function(item){
  return item.a=='something';
}).length);
Mihai Alexandru-Ionut
  • 47,092
  • 13
  • 101
  • 128
2

The cleanest way i found was using a single reduce. No need to reverse the array or using multiple loops

const items = [
  {'a': 'something', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12}
];

function findLastIndex(items, callback) {
  return items.reduce((acc, curr, index) => callback(curr) ? index : acc, 0);
}

console.log(findLastIndex(items, (curr) => curr.a === "something"));
  • 1
    This is a good solution, however the initial value of the reducer should be -1, and not 0. – dmarr May 10 '22 at 22:54
2

You can just map the the list to a and then use lastIndexOf:

const array = [
  {'a': 'something', 'b':12},
  {'a': 'something', 'b':12},
  {'a': 'somethingElse', 'b':12},
  {'a': 'something', 'b':12},
  {'a': 'somethingElse', 'b':12}
];

const result = array.map(({a}) => a).lastIndexOf('something');
console.log(result);
Paul
  • 21
  • 1
1
var arr = [
   {'a': 'something', 'b':12},
   {'a': 'something', 'b':12},
   {'a': 'somethingElse', 'b':12},
   {'a': 'something', 'b':12},
   {'a': 'somethingElse', 'b':12}
];

var item_count = 0;
var traverse_count = 0;
var last_item_traverse_count = 0;    
arr = arr.reverse();

arr.filter(function(element) {
   traverse_count += 1;
   if(item_count < 1 && element.a == 'something') {
       last_item_traverse_count = traverse_count;
       item_count += 1;
       return true;
   }

   return false;
});

var item_last_index = arr.length - last_item_traverse_count;

console.log(item_last_index);

I hope this code definitely works. The code may not follow naming conventions. I'm sorry about that. You can just name those variables however you want.

Wolverine
  • 1,712
  • 1
  • 15
  • 18
1

here what i used to get the last active element :

return this._scenarios.reverse().find(x => x.isActive);
  • `reverse` mutates the array which is probably a side effect the OP doesn't want. Plus it's particularly inefficient! – Pierre Jun 16 '21 at 20:54
1

Simple and fast:

const arr = [
  {'a': 'something', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12}
];

let i = arr.length;
while(i--) if (arr[i] === 'something') break;

console.log(i);  //last found index or -1

cid
  • 11
  • 1
1

Update - 27 October 2021 (Chrome 97+)

You can now use findLastIndex:

const array = [
  {'a': 'something', 'b':12},
  {'a': 'something', 'b':12},
  {'a': 'somethingElse', 'b':12},
  {'a': 'something', 'b':12},
  {'a': 'somethingElse', 'b':12}
];

const last_index = array.findLastIndex((item) => item.a === 'something');
// → 3

Read more here.

NeNaD
  • 18,172
  • 8
  • 47
  • 89
0

You can use reverse and map together to prevent from mutating the original array.

var arr = [{'a': 'something', 'b':12},
{'a': 'something', 'b':12},
{'a': 'somethingElse', 'b':12},
{'a': 'something', 'b':12},
{'a': 'somethingElse', 'b':12}];

var index = arr.length - 1 - arr.map(x => x)
   .reverse()
   .findIndex(x => (x.a === 'something'))
if (index === arr.length) {
  index = -1;
}
Iman Mohamadi
  • 6,552
  • 3
  • 34
  • 33
  • But what happened if the object does not exist. It returns the length of the array instead of "-1". So all object a's value = 'something' and all object a's value != 'something' returns the same result(length of the array). – Sachin Kumar Nov 29 '18 at 10:19
  • 1
    @sachinkumar nice point but you know that in this case if you get 5, It can't be an index since your array indexes are in this case from 0 to 4. If you want to get -1 when you search, you can throw an extra line at the bottom to check if string is not found. I edited my answer to have that feature too. – Iman Mohamadi Nov 29 '18 at 21:46
0

const arrSome = [{'a': 'something', 'b':12},
    {'a': 'something', 'b':12},
    {'a': 'somethingElse', 'b':12},
    {'a': 'something', 'b':12},
    {'a': 'somethingElse', 'b':12}]

const lastIndex = arrSome.reverse().findIndex(el=> el.a == 'something');//?
let index;
if (~lastIndex) {
    index =arrSome.length - lastIndex;
} else {
    index = -1
}

console.log(index) 
bad4iz
  • 220
  • 2
  • 4
0

You can use Math.max apply to the array that match your condition with Array.prototype.map()

like this example

private findLastIndex() {
    const filter = this.myArray.map((t, index) => {
      if (
        // your condition
      ) {
        return index;
      } else {
        return -1;
      }
    });

    return Math.max.apply(null, filter);
  }
0

Here's a solution using no (manual) loops, and without mutating the calling array:

const arr = [
  {'a': 'something', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12},
  {'a': 'something', 'b': 12},
  {'a': 'somethingElse', 'b': 12}
];

function findLastIndex(arr, callback, thisArg) {
  return (arr.length - 1) - // Need to subtract found, backwards index from length
    arr.reduce((acc, cur) => [cur, ...acc], []) // Get reversed array
    .findIndex(callback, thisArg); // Find element satisfying callback in rev. array
}

console.log(findLastIndex(arr, (e) => e.a === "something"));

Reference:

It should be noted that the reducer used here is vastly outperformed by arr.slice().reverse(), as used in @Rajesh's answer.

zcoop98
  • 2,590
  • 1
  • 18
  • 31
-4

why not just get the length and use as a array pointer e.g.

var arr = [ 'test1', 'test2', 'test3' ];

then get the last index of the array by getting the length of the array and minus '1' since array index always starts at '0'

var last arrIndex = arr[arr.length - 1];
Juliver Galleto
  • 8,831
  • 27
  • 86
  • 164