67

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?

3

23 Answers 23

86

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;
}
Sign up to request clarification or add additional context in comments.

3 Comments

No copies, no reverse, no useless filter, O(n). Definitive.
question are asking in js, so answer should be in js, even though ts and js are convertible. like: stackoverflow.com/a/33269005/5858238
You could have put all the type information in the JSDoc comment. Also, it doesn't mirror the signature - you're missing the thisArg.
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')

5 Comments

It works but i don't understand. What did you do with the slice without params? can you explain me the logic, please?
@IvanLencina array.reverse reverses an arrray, so to prevent modifying original array, i made a copy of it using slice
Oh, I see. Thank you :) I implemented your solution
If the element is not found (-1), finalIndex will be the size of the array, not -1. Need to add that case to be complete.
It's more functional but slicing and reversing the array will be significantly slower than iterating backwards
18
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})

4 Comments

No loops if possible?
This is more expensive. Ultimately doesn't matter much though.
@Jacob without loop you can find index, even indexOf using while loop
Note that reverse() changes also yourArray (it's an in place destructive operation) developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
17

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

Comments

14

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

2 Comments

No loops if possible?
no, not without any loop. and no short circuit with other array methods from right.
6

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

1 Comment

Node 18 supports this.
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")

Comments

4

This is what I used:

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

1 Comment

This is not supporting the -1 case
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;
    }
}

Comments

3

What about something like this in ES6:

  arr.indexOf(arr.filter(item => item.a === 'something').pop())

Comments

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

3 Comments

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.
Could you please tell me what does (!cond) mean?
@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.
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);

4 Comments

A reverse/filter is basically a loop. Just because it's hidden behind a method doesn't change that.
@EvanTrimboli it's impossible to find element in array without loop, even indexOf method use while loop
I know, the original text of the answer said "this is without a loop", which isn't really correct.
This answer looks wrong to me. The result in the code snippet is correct only by chance. Swap the last two lines to see what I mean.
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 Comment

This is a good solution, however the initial value of the reducer should be -1, and not 0.
2

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

2 Comments

Are you sure the code works ? Specially for the while conditions that seems wrong. It's not while(i-- > 0) ?
It is the best answer. The only your solution is well structured and fast
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);

Comments

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.

Comments

1

here what i used to get the last active element :

return this._scenarios.reverse().find(x => x.isActive);

1 Comment

reverse mutates the array which is probably a side effect the OP doesn't want. Plus it's particularly inefficient!
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.

5 Comments

Please keep in mind this is not yet supported by some major browsers (like firefox & safari): caniuse.com/?search=findLastIndex%20
Tnx! I updated my answer to highlight the Chrome for now.
Node 18 - released today- supports it.
And by the way, you could also flag this question as duplicate so that you don’t have to repeat your answer.
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;
}

2 Comments

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).
@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.
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) 

Comments

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

Comments

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.

Comments

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

1 Comment

The question was to find the last index where "a" has the value "something", not the last value...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.