Find last matching object in array of objects - javascript

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?

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

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

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/

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

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

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.

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

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

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

plain JS:
var len = fruits.length, prev = false;
while(!prev && len--){
(fruits[len].shape == currentShape) && (prev = fruits[len]);
}
lodash:
_.findLast(fruits, 'shape', currentShape);

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

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

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

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

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.

Related

Best way to re arrange array of object?

I have an array or object which will serve as columns for a table with a unique key
const list = [{
key: "Name",
textColor: "red"
},{
key: "Age",
textColor: "green"
},{
key: "Occupation",
textColor: "yellow"
}]
And, I have a list of ordering of columns in the table
const newOrder = ["Occupation", "Name", "Age"]
Now , how can i rearrange the list according to the newOrder without using nested loops. Also, these all are dyanamic, so its not just about the above mentioned three columns
Expected Output
const list = [{
key: "Occupation",
textColor: "yellow"
},{
key: "Name",
textColor: "red"
},{
key: "Age",
textColor: "green"
}]
Your list can be reformatted to a regular javascript object in which key is the property name, and textColor is the value:
const toObject = kvps => Object.fromEntries(kvps.map(kvp => [ kvp.key, kvp.textColor ]));
With a given array of keys, you can pick values from that object like so:
const fromObject = (order, obj) => order.map(key => ({ key, textColor: obj[key] }));
Chain the two together, and you can reorder any list of key value pairs:
const list = [{
key: "Name",
textColor: "red"
},{
key: "Age",
textColor: "green"
},{
key: "Occupation",
textColor: "yellow"
}]
const toObject = kvps => Object.fromEntries(kvps.map(kvp => [ kvp.key, kvp.textColor ]));
const fromObject = (order, obj) => order.map(key => ({ key, textColor: obj[key] }));
const reorder = (order, kvps) => fromObject(order, toObject(kvps));
const newList = reorder(["Occupation", "Name", "Age"], list);
console.log(
newList
)
Edit: if the sizes of your list and order arrays are small, you probably want to go with the much easier to read approach suggested by Jon Webb in one of the other answers. 🙂 I tried to keep my solution to an O(n + m) complexity rather than O(n * m), (n = list size, m = order size) but it's probably not worth the added complexity.
You can iterate on the "order" list, finding the corresponding item in the original list, and push the item to the new list in that order:
const orderList = (list, order) => {
const newList = [];
for (key of order) {
const item = list.find((obj) => obj.key == key);
if (item) newList.push(item);
}
return newList;
}
You can use the sort method on the array. The sort method will sort in place so you should copy your array if you dont want to mutate the original.
The array sort method takes a compare function the receives two elements for comparison a and b. It should return a number and it will sort them depending on that number:
If > 0 then b is before a
If < 0 then b is after a
If 0 then keep as is
By using indexOf on the newOrder array can get the index of the key. And index of 0 should come before and index of 1 should come before and index of 2 of course. So if the index of a.key is 2 and the index of b.key is 0, then we should return a value greater than 0 since b should come before a.
In my implementation below I'm cloning the original list ([...list]) as to not mutate accidentally. You could just as well do list.sort(...) if you don't need or care about mutating.
const list = [{
key: "Name",
textColor: "red"
},{
key: "Age",
textColor: "green"
},{
key: "Occupation",
textColor: "yellow"
}]
const newOrder = ["Occupation", "Name", "Age"]
function sort(list, order) {
return [...list].sort((a, b) => {
const keyIndexA = order.indexOf(a.key);
const keyIndexB = order.indexOf(b.key);
if (keyIndexA < keyIndexB) return -1;
if (keyIndexA > keyIndexB) return 1;
return 0;
});
}
console.log(sort(list, newOrder));
You can use just regular sort
const list = [{key: "Name",textColor: "red"},{key: "Age",textColor: "green"},{key: "Occupation",textColor: "yellow"}];
const newOrder = ["Occupation", "Name", "Age"];
const result = list.sort(({key: a}, {key: b}) => newOrder.indexOf(a) - newOrder.indexOf(b));
console.log(result);
.as-console-wrapper{min-height: 100%!important; top: 0}
You can try to loop through the newOrder array, find the object that correlates to the first item and push to a new array.
const orderedList = [];
newOrder.forEach(order => {
orderedList.push(list.find(({key}) => key === order));
})
You can use orderBy from lodash library

Move an element to the front of an array: Javascript [duplicate]

This question already has answers here:
Move an array element from one array position to another
(44 answers)
Closed 2 years ago.
I have an array of objects with this format
let arr = [ { name: "test1", id: 5}, { name: "test2", id: 6 } , { name: "test3", id: 8 } ]
Now I basically want to move the item with 6 to the front of the array by re-arranging so that result becomes
let result = [ { name: "test2", id: 6 } , { name: "test1", id: 5}, { name: "test3", id: 8 } ]
What I have tried
const found = arr.find((element) => {
return element.id === 6;
});
if (found) {
const [...arr, found] = arr;
return found;
} else {
return arr;
}
You can make use of Array.unshift and Array.splice.
let arr = [{name:"test1",id:5},{name:"test2",id:6},{name:"test3",id:8}]
const moveToFront = (data, matchingId) => {
//find the index of the element in the array
const index = data.findIndex(({id}) => id === matchingId);
if(index !== -1) {
//if the matching element is found,
const updatedData = [...data];
//then remove that element and use `unshift`
updatedData.unshift(...updatedData.splice(index, 1));
return updatedData;
}
//if the matching element is not found, then return the same array
return data;
}
console.log(moveToFront(arr, 6));
.as-console-wrapper {
max-height: 100% !important;
}
You could sort the array with the delta of the checks.
const
array = [{ name: "test1", id: 5 }, { name: "test2", id: 6 }, { name: "test3", id: 8 }];
array.sort((a, b) => (b.id === 6) - (a.id === 6));
console.log(array);
const array = [{ name: "test1", id: 5 }, { name: "test2", id: 6 }, { name: "test3", id: 8 }];
const sortedArray = array.sort((a, b) => (b.id === 6) - (a.id === 6)); console.log(sortedArray);
discusses an easy way to sort your JavaScript Array by the order of the index number for each item in the Array object. This can be helpful if you want to sort alphabetically and not need to create a new String Array with a function like String.sort().
A quick tip that can be useful to you if you want a quick solution to sorting an Array in JavaScript is below.Remember that it is always best practice to use the Array methods built into the language. They are created to work fast and efficient. However, if you really want to sort your array by index number and not have an array of strings, then this article will be for you.:
String→Number: When a function returns a Number value, then JavaScript interprets it as being equal to the Number value in the code...The function is used by passing two parameters, which should return true when they are equal and false when they are not equal.In this case, we are sort of reverse comparing them. We are checking the ID of the items inside the Array to see if they match, but we subtract one to check if they are less than. This is because when we call .sort(), JavaScript is sorting alphabetically and an ID with a value of 6 will be at the end of the list. So, a value of -1 will make it appear in the correct order.If you want to use this method for your Array, then please add a comment below!
You can use Array.unshift() to add element to the beginning of the array and Array.splice() to remove the array element.
let arr = [ { name: "test1", id: 5}, { name: "test2", id: 6 } , { name: "test3", id: 8 } ]
let result = [...arr];
const index = result.findIndex(e => e.id === 6)
result.unshift(result.splice(index, 1)[0])
console.log(result);
You can make use of filter and unshift
let arr = [{ name: "test1", id: 5 },{ name: "test2", id: 6 },{ name: "test3", id: 8 }];
let firstObject;
let result = arr.filter((value) => {
if (value.id != 6) return value;
firstObject = value;
});
result.unshift(firstObject);
console.log(result);

How to filter array of objects and get filtered object value

I have an array model as below:
nodes:[
{ id: 1, label: 'label1'},
{ id: 2, label: 'label2'},
{ id: 3, label: 'label3'}
]
I whant to get the label of node filtering by id
I tried the next way, but dont get it work
const selectedNode = 2;
const nodeLabel = nodes.filter(({id}) => id.label ? id === selectedNode) // its filter here
You can use find method by passing a callback provided function.
The find() method returns the value of the first element in the array that passed the provided testing function. Otherwise undefined is returned.
let nodes=[
{ id: 1, label: 'label1'},
{ id: 2, label: 'label2'},
{ id: 3, label: 'label3'}
];
let id=2;
let node = nodes.find(a=>a.id == id);
console.log(node ? node.label : 'id not found');
nodes.find(node => node.id === selectedNode).label
You were quite close.
This line
nodes.filer(({id}) => id.label ? id === selectedNode)
has few issues (assuming filer was just a typo)
It is comparing an integer with an object. (id is the object here)
filter will give you the list of objects rather than its property label.
You were comparing label with id value.
{id} to be replaced by id.
Just modify this to
nodes.filter( (id) => id.id === selectedNode )[0].label
Demo
var nodes = [
{ id: 1, label: 'label1'},
{ id: 2, label: 'label2'},
{ id: 3, label: 'label3'}
];
var selectedNode = 2;
console.log( nodes.filter( (id) => id.id === selectedNode )[0].label );
There's a few ways you could do what you're trying to do. Here are a couple using native Array methods.
Chain filter and map and destructure the returned array.
const [nodeLabel] = nodes
.filter(({id}) => id === selectedNode)
.map(({label}) => label)
Use reduce
const nodeLabel = nodes
.reduce((returnValue, item) => {
if (item.id === selectedNode) return item.label
return returnValue
})

How to turn the following recursive function into a pure function?

The following function appends an object into a nested array (by searching for it recursively):
function appendDeep (arr, obj, newObj) {
if (arr.indexOf(obj) !== -1) {
arr.splice(arr.indexOf(obj) + 1, 0, newObj)
} else {
arr.map(item => {
if (item.children) spliceDeep(item.children, obj)
})
}
}
Example:
const colors = {
children: [
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
}
]
}
]
}
const color = {
name: 'black'
}
const newColor = {
name: 'brown'
}
appendDeep(colors.children, color, newColor)
Result:
children: [
[
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
},
{
name: 'brown'
}
]
}
]
]
As you can see appendDeep returns a side-effect; it modifies arr. So I decided to return the array instead (so the function would become pure):
function findDeep (arr, obj) {
if (arr.indexOf(obj) !== -1) {
console.log(arr)
return arr
} else {
arr.map(item => {
if (item.children) findDeep(item.children, obj)
})
}
}
And use the new function like this:
const newArr = findDeep(colors.children, color)
newArr.splice(newArr.indexOf(color) + 1, 0, newColor)
But I get this error:
bundle.js:19893 Uncaught TypeError: Cannot read property 'splice' of undefined
What I'm a doing wrong?
(Note: Here's the CodePen.)
(Note 2: console.log(arr) does return the nested children. But for some reason they become undefined outside of the function.)
You are not returning you recursive findDeep method within the map. Return that for the recursion to work because your conditional branch is not returning anything from within map. As a result you are getting the result as undefined. JSBin
First, a find method that will return the array in which the requested item is located (as a direct child).
function findDeep(arr, obj) {
return arr.map((item) => {
if (item.name === obj.name) {
return arr;
} else if (item.children) {
return findDeep(item.children, obj);
} else {
return undefined;
}
}).reduce((prev, cur) => {
return prev ? prev : cur;
});
}
You could use that to append items to the list, but that will still modify the original array:
function appendDeep(arr, color, newColor) {
let found = findDeep(arr, color);
if (found) {
found.splice(found.indexOf(color) + 1, 0, newColor);
}
return arr;
}
If you don't want to modify the original array, things get more complex. That's because the standard array functions such as push and splice will modify the original array. There's no quick fix, at least not that I know of, because preferably you would not want to clone any more items than you really have to.
You don't need to clone black, but you do need to clone the array that contains it (It can simply reuse the existing object for black.) That means the object for yellow also needs to be cloned (to use the cloned array) and the array in which yellow is located needs to be cloned. But white, which is in the same array, is not modified and does not need to be cloned. I've not figured out how to do that properly.
This is a proposal which uses thisArgs of Array#some.
function appendDeep(object, search, insert) {
function iter(a) {
if (a.name === search.name) {
this.children.push(insert);
return true;
}
return Array.isArray(a.children) && a.children.some(iter, a);
}
object.children.some(iter, object);
}
var colors = { children: [{ name: 'white', }, { name: 'yellow', children: [{ name: 'black' }] }] },
color = { name: 'black' },
newColor = { name: 'brown' };
appendDeep(colors, color, newColor);
document.write('<pre>' + JSON.stringify(colors, 0, 4) + '</pre>');

Check if an array contains a specified object

The following function searches an object recursively through an object that has nested arrays:
function findDeep(arr, obj) {
console.log(arr)
if (arr.indexOf(obj) !== -1) {
console.log(arr)
return arr
} else {
arr.forEach(item => {
if (item.children) findDeep(item.children, obj)
})
}
}
const colors = {
children: [
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
}
]
}
]
}
const color = {
name: 'black'
}
findDeep(colors.children, color)
The first console.log(arr) do log the matched array:
[
{ name: 'black' }
]
But he second console.log(arr) doesn't log anything. Shouldn't arr.indexOf(obj) return 1, and therefore make the second console.log(arr) log the array?
Here's the CodePen.
You can not find index of object in array using indexOf unless both the objects(passed in indexOf to test and present in array) are pointing to the same reference.
For example:
var a = {
a: 10
};
var b = [{
a: 10
}, {
b: 20
}];
console.log(b.indexOf(a)); // Object `a` and Object in `0th` index of the array are having similar `key-values`
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
But,
var a = {
a: 10
};
var b = [a, {
b: 20
}];
//`0th` index in the array is nothing but a variable holding `object`
console.log(b.indexOf(a)); //Same variable is tested in `indexOf`
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
From the docs, indexOf() compares searchElement to elements of the Array using strict equality (the same method used by the === or triple-equals operator).
{} === {} will be evaluated as false because,
An expression comparing Objects is only true if the operands reference the same Object. If both operands are objects, then JavaScript compares internal references which are equal when operands refer to the same object in memory.[Ref]
There are few solutions and approaches but all of them will be doing iteration and comparing value of the key in object. Refer this answer

Categories