73

I have an array of objects. I need to get the object type ("shape" in this example) of the last object, remove it, and then find the index of the previous object in the array that has the same type, e.g. "shape".

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

So, obviously the type in this example will be "round". But I'm not looking for an array value of "round". I'm looking for where fruits.shape = round.

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

But just using that doesn't work. I'm sure I'm missing something simple. How do I find the last item in the array where the shape of the object = round?

Graeck
  • 1,326
  • 1
  • 11
  • 15

15 Answers15

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

or newer ECMAscript specification provides immutable method Array.prototype.toReversed()

const fruit = fruits.toReversed().find(fruit => fruit.shape === currentShape);
Luke Liu
  • 1,387
  • 2
  • 8
  • 6
  • This is much cleaner than the accepted answer. Functional and clean! – nikjohn Oct 11 '19 at 20:06
  • 61
    And for those wondering why the slice() is needed: reverse() is mutating! slice() gives you a copy to work on. – Michael Rush Nov 01 '19 at 15:47
  • 7
    can be done like `const fruit = [...fruits].reverse().find(fruit => fruit.shape === currentShape); ` – Joe Lloyd Jan 15 '20 at 13:12
  • 4
    maybe looks cleaner than the accepted answer but copying and reversing an array is not an efficient way of finding an element. – Pawel Jul 19 '20 at 18:29
  • 5
    @Pawel depends on the size of the array and how often the operation is performed. Performance win on a micro level is usually an OK trade-off for readability and maintainability imo – kano Oct 22 '20 at 20:02
  • 1
    @kano I disagree in this case. The accepted answer is quite readable. It's a simple for loop. What is not readable about a simple for loop? – dudewad Oct 31 '20 at 00:34
  • 2
    Readability is always relative (as it is subjective). To me this solution requires much less cognitive load to parse vs the for loop. I'm not saying the for loop is hard to read, but this solution could be understood by someone with no coding background, as it almost reads like English or pseudo-code. I've come to value the reading experience of code a lot more in the last years and would prefer this solution over the for loop (even if the latter is more performant). Just my 2 cents on the matter. – kano Nov 03 '20 at 10:56
65

You can transform your array to an array boolean type and get the last true index.

const lastIndex = fruits.map(fruit => 
  fruit.shape === currentShape).lastIndexOf(true);
Den
  • 1,424
  • 16
  • 18
26
var previousInShapeType, index = fruits.length - 1;
for ( ; index >= 0; index--) {
    if (fruits[index].shape == currentShape) {
        previousInShapeType = fruits[index];
        break;
    }
}

You can also loop backwards through array.

Fiddle: http://jsfiddle.net/vonn9xhm/

AtheistP3ace
  • 9,611
  • 12
  • 43
  • 43
  • 13
    This is the only correct answer in here. Do people have no regard to efficient anymore? – Artless Oct 21 '15 at 20:56
  • 2
    agree, functional programming has destroyed efficiency I'd say – EugenSunic Mar 11 '20 at 13:05
  • 3
    this is the fastest solution performing at 55,323,764 ops/s comparing to average of 2,200 ops/s for solutions below (25 000x faster) - (tested on collection of 100k fruits on latest google chrome).BUT! If we don't care about cost of reversing collection once and we cache the reversed copy then .reverse().find solution is actually faster performing at 55,853,952 ops/s – Adassko Jun 17 '20 at 16:44
  • 2
    Also, in most cases code should be optimized for maintainability not efficiency unless you absolutely need it. If you know you will have a short list, use the readable approach. – Marcel May 27 '21 at 21:17
  • 3
    Premature optimization can destroy maintainability which in turn can destroy optimizations where it really counts. Don't sacrifice maintainability for negligible optimizations. – JakeDK Jul 31 '21 at 08:06
  • 3
    @Artless is so efficient, that he wrote "to" instead of "for" and "efficient" instead of "efficiency", saving two whole characters! – Arik Nov 25 '21 at 19:18
22

Using the Lodash library, you can find the last logical element.

_.findLast([1,2,3,5,4], n => n % 2 == 1); // Find last odd element
// expected output: 5
AMJ
  • 125
  • 1
  • 6
Anupam Maurya
  • 1,927
  • 22
  • 26
16

Update - 27 October 2021 (Chrome 97+)

Proposal for Array.prototype.findLast and Array.prototype.findLastIndex is now on Stage 3 4!

Here's how you can use those:

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

You can read more in this V8 blog post.

You can find more in "New in Chrome" series.

Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
NeNaD
  • 18,172
  • 8
  • 47
  • 89
13

An easier and relatively efficient solution. Filter and pop!

Filter all fruits matching the current shape and then pop to get the last one.

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);
Vikram Deshmukh
  • 12,304
  • 4
  • 36
  • 38
11

This is a solution that does not depend on reverse, and therefore does not require "cloning" the original collection.

const lastShapeIndex = fruits.reduce((acc, fruit, index) => (
    fruit.shape === currentShape ? index : acc
), -1);
Ed I
  • 7,008
  • 3
  • 41
  • 50
  • this is actually the most elegant solution in pure javascript. I don't know why it got downvoted – Adassko Jun 17 '20 at 16:03
  • It should not initialize with 0. Maybe -1, since it is a de-factor standard for index lookup. Also - the resulting `const` isn't the fruit object itself, but the index. – avioli Oct 25 '20 at 22:29
  • 2
    @avioli, thank you for both of those points. I edited the answer with those changes. – Ed I Dec 03 '20 at 10:58
4

Based on Luke Liu's answer, but using ES6's spread operator to make it a bit easier to read:

const fruit = [...fruits].reverse().find(fruit => fruit.shape === currentShape);
Yulian
  • 6,262
  • 10
  • 65
  • 92
3

Update - Array.prototype.findLast() is now available for use

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

**Please check out browser compatibly before using it in this link

Read more about findLast here

Another way to achieve this is using the reverse (but less efficient)

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);
Ran Turner
  • 14,906
  • 5
  • 47
  • 53
2

plain JS:

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

lodash:

_.findLast(fruits, 'shape', currentShape);
evilive
  • 1,781
  • 14
  • 20
2

While the currently accepted answer will do the trick, the arrival of ES6 (ECMA2015) added the spread operator which makes it easy to duplicate your array (this will work fine for the fruit array in your example but beware of nested arrays). You could also make use of the fact that the pop method returns the removed element to make your code more concise. Hence you could achieve the desired result with the following 2 lines of code

const currentShape = fruits.pop().shape;
const previousInShapeType = [...fruits].reverse().find(
  fruit => fruit.shape === currentShape
);
Willem Dehaes
  • 81
  • 1
  • 8
1

I would suggest another nice solution which doesn't bother cloning a new object using reverse().

I use reduceRight to does the job instead.

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

And usage

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

Krit
  • 610
  • 1
  • 7
  • 18
1

Here's a typescript version:

/**
 * 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;
}

Usage:

const r = findLast([12, 43, 5436, 44, 4], v => v < 45);
console.log(r); // 4
meblum
  • 1,654
  • 12
  • 23
0

You should use filter! filter takes a function as an argument, and returns a new array.

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

Now roundFruits will contain the elements of the original array for which the function returns true. Now if you want to know the original array indexes, never fear - you can use the function map. map also operates on an array, and takes a function which acts on the array. we can chain map and filter together as follows

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

The resulting array will contain all objects in the original fruits array for which the shape is round, and their original index in the fruits array.

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

Now you can do whatever you need to with the exact knowledge of the location of the relevant data.

// get last round element
fruits[4];
Jonah Williams
  • 20,499
  • 6
  • 65
  • 53
  • If I'm not concerned with using some additional memory etc, I tend to use `filter()` and `pop()`, so something like `arr.filter(({shape) => shape === 'round').pop()`. – JHH May 20 '19 at 12:42
0

findLastIndex and findLast are now natively supported across all major browsers (except IE).

Referring to your example, you can find the index of the last item that matches your condition as follows:

var previousInShapeType = fruits.findLastIndex((fruit) => fruit.shape === currentShape);

findLast works exactly the same but returns an object instead.

Reference to MDN documentation for findLastIndex and findLast.

Ovidijus Parsiunas
  • 2,512
  • 2
  • 8
  • 18