Get the index of the object inside an array, matching a condition - javascript

I have an array like this:
[{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"},...]
How can I get the index of the object that matches a condition, without iterating over the entire array?
For instance, given prop2=="yutu", I want to get index 1.
I saw .indexOf() but think it's used for simple arrays like ["a1","a2",...]. I also checked $.grep() but this returns objects, not the index.

As of 2016, you're supposed to use Array.findIndex (an ES2015/ES6 standard) for this:
a = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}];
index = a.findIndex(x => x.prop2 ==="yutu");
console.log(index);
It's supported in Google Chrome, Firefox and Edge. For Internet Explorer, there's a polyfill on the linked page.
Performance note
Function calls are expensive, therefore with really big arrays a simple loop will perform much better than findIndex:
let test = [];
for (let i = 0; i < 1e6; i++)
test.push({prop: i});
let search = test.length - 1;
let count = 100;
console.time('findIndex/predefined function');
let fn = obj => obj.prop === search;
for (let i = 0; i < count; i++)
test.findIndex(fn);
console.timeEnd('findIndex/predefined function');
console.time('findIndex/dynamic function');
for (let i = 0; i < count; i++)
test.findIndex(obj => obj.prop === search);
console.timeEnd('findIndex/dynamic function');
console.time('loop');
for (let i = 0; i < count; i++) {
for (let index = 0; index < test.length; index++) {
if (test[index].prop === search) {
break;
}
}
}
console.timeEnd('loop');
As with most optimizations, this should be applied with care and only when actually needed.

How can I get the index of the object tha match a condition (without iterate along the array)?
You cannot, something has to iterate through the array (at least once).
If the condition changes a lot, then you'll have to loop through and look at the objects therein to see if they match the condition. However, on a system with ES5 features (or if you install a shim), that iteration can be done fairly concisely:
var index;
yourArray.some(function(entry, i) {
if (entry.prop2 == "yutu") {
index = i;
return true;
}
});
That uses the new(ish) Array#some function, which loops through the entries in the array until the function you give it returns true. The function I've given it saves the index of the matching entry, then returns true to stop the iteration.
Or of course, just use a for loop. Your various iteration options are covered in this other answer.
But if you're always going to be using the same property for this lookup, and if the property values are unique, you can loop just once and create an object to map them:
var prop2map = {};
yourArray.forEach(function(entry) {
prop2map[entry.prop2] = entry;
});
(Or, again, you could use a for loop or any of your other options.)
Then if you need to find the entry with prop2 = "yutu", you can do this:
var entry = prop2map["yutu"];
I call this "cross-indexing" the array. Naturally, if you remove or add entries (or change their prop2 values), you need to update your mapping object as well.

What TJ Crowder said, everyway will have some kind of hidden iteration, with lodash this becomes:
var index = _.findIndex(array, {prop2: 'yutu'})

var CarId = 23;
//x.VehicleId property to match in the object array
var carIndex = CarsList.map(function (x) { return x.VehicleId; }).indexOf(CarId);
And for basic array numbers you can also do this:
var numberList = [100,200,300,400,500];
var index = numberList.indexOf(200); // 1
You will get -1 if it cannot find a value in the array.

var index;
yourArray.some(function (elem, i) {
return elem.prop2 === 'yutu' ? (index = i, true) : false;
});
Iterate over all elements of array.
It returns either the index and true or false if the condition does not match.
Important is the explicit return value of true (or a value which boolean result is true). The single assignment is not sufficient, because of a possible index with 0 (Boolean(0) === false), which would not result an error but disables the break of the iteration.
Edit
An even shorter version of the above:
yourArray.some(function (elem, i) {
return elem.prop2 === 'yutu' && ~(index = i);
});

Using Array.map() and Array.indexOf(string)
const arr = [{
prop1: "abc",
prop2: "qwe"
}, {
prop1: "bnmb",
prop2: "yutu"
}, {
prop1: "zxvz",
prop2: "qwrq"
}]
const index = arr.map(i => i.prop2).indexOf("yutu");
console.log(index);

The best & fastest way to do this is:
const products = [
{ prop1: 'telephone', prop2: 996 },
{ prop1: 'computadora', prop2: 1999 },
{ prop1: 'bicicleta', prop2: 995 },
];
const index = products.findIndex(el => el.prop2 > 1000);
console.log(index); // 1

I have seen many solutions in the above.
Here I am using map function to find the index of the search text in an array object.
I am going to explain my answer with using students data.
step 1: create array object for the students(optional you can create your own array object).
var students = [{name:"Rambabu",htno:"1245"},{name:"Divya",htno:"1246"},{name:"poojitha",htno:"1247"},{name:"magitha",htno:"1248"}];
step 2: Create variable to search text
var studentNameToSearch = "Divya";
step 3: Create variable to store matched index(here we use map function to iterate).
var matchedIndex = students.map(function (obj) { return obj.name; }).indexOf(studentNameToSearch);
var students = [{name:"Rambabu",htno:"1245"},{name:"Divya",htno:"1246"},{name:"poojitha",htno:"1247"},{name:"magitha",htno:"1248"}];
var studentNameToSearch = "Divya";
var matchedIndex = students.map(function (obj) { return obj.name; }).indexOf(studentNameToSearch);
console.log(matchedIndex);
alert("Your search name index in array is:"+matchedIndex)

You can use the Array.prototype.some() in the following way (as mentioned in the other answers):
https://jsfiddle.net/h1d69exj/2/
function findIndexInData(data, property, value) {
var result = -1;
data.some(function (item, i) {
if (item[property] === value) {
result = i;
return true;
}
});
return result;
}
var data = [{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"}]
alert(findIndexInData(data, 'prop2', "yutu")); // shows index of 1

function findIndexByKeyValue(_array, key, value) {
for (var i = 0; i < _array.length; i++) {
if (_array[i][key] == value) {
return i;
}
}
return -1;
}
var a = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}];
var index = findIndexByKeyValue(a, 'prop2', 'yutu');
console.log(index);

Try this code
var x = [{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"}]
let index = x.findIndex(x => x.prop1 === 'zxvz')

Another easy way is :
function getIndex(items) {
for (const [index, item] of items.entries()) {
if (item.prop2 === 'yutu') {
return index;
}
}
}
const myIndex = getIndex(myArray);

Georg have already mentioned ES6 have Array.findIndex for this.
And some other answers are workaround for ES5 using Array.some method.
One more elegant approach can be
var index;
for(index = yourArray.length; index-- > 0 && yourArray[index].prop2 !== "yutu";);
At the same time I will like to emphasize, Array.some may be implemented with binary or other efficient searching technique. So, it might perform better over for loop in some browser.

Why do you not want to iterate exactly ? The new Array.prototype.forEach are great for this purpose!
You can use a Binary Search Tree to find via a single method call if you want. This is a neat implementation of BTree and Red black Search tree in JS - https://github.com/vadimg/js_bintrees - but I'm not sure whether you can find the index at the same time.

One step using Array.reduce() - no jQuery
var items = [{id: 331}, {id: 220}, {id: 872}];
var searchIndexForId = 220;
var index = items.reduce(function(searchIndex, item, index){
if(item.id === searchIndexForId) {
console.log('found!');
searchIndex = index;
}
return searchIndex;
}, null);
will return null if index was not found.

var list = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}
];
var findProp = p => {
var index = -1;
$.each(list, (i, o) => {
if(o.prop2 == p) {
index = i;
return false; // break
}
});
return index; // -1 == not found, else == index
}

Related

push only unique elements in an array

I have array object(x) that stores json (key,value) objects. I need to make sure that x only takes json object with unique key. Below, example 'id' is the key, so i don't want to store other json objects with 'item1' key.
x = [{"id":"item1","val":"Items"},{"id":"item1","val":"Items"},{"id":"item1","val":"Items"}]
var clickId = // could be "item1", "item2"....
var found = $.inArray(clickId, x); //
if(found >=0)
{
x.splice(found,1);
}
else{
x.push(new Item(clickId, obj)); //push json object
}
would this accomplish what you're looking for? https://jsfiddle.net/gukv9arj/3/
x = [
{"id":"item1","val":"Items"},
{"id":"item1","val":"Items"},
{"id":"item2","val":"Items"}
];
var clickId = [];
var list = JSON.parse(x);
$.each(list, function(index, value){
if(clickId.indexOf(value.id) === -1){
clickId.push(value.id);
}
});
You can't use inArray() because you are searching for an object.
I'd recommend rewriting a custom find using Array.some() as follows.
var x = [{"id":"item1","val":"Items"},{"id":"item1","val":"Items"},{"id":"item1","val":"Items"}]
var clickId = "item1";
var found = x.some(function(value) {
return value.id === clickId;
});
alert(found);
Almost 6 years later i ended up in this question, but i needed to fill a bit more complex array, with objects. So i needed to add something like this.
var values = [
{value: "value1", selected: false},
{value: "value2", selected: false}
//there cannot be another object with value = "value1" within the collection.
]
So I was looking for the value data not to be repeated (in an object's array), rather than just the value in a string's array, as required in this question. This is not the first time i think in doing something like this in some JS code.
So i did the following:
let valueIndex = {};
let values = []
//I had the source data in some other and more complex array.
for (const index in assetsArray)
{
const element = assetsArray[index];
if (!valueIndex[element.value])
{
valueIndex[element.value] = true;
values.push({
value: element.value,
selected: false
});
}
}
I just use another object as an index, so the properties in an object will never be repated. This code is quite easy to read and surely is compatible with any browser. Maybe someone comes with something better. You are welcome to share!
Hopes this helps someone else.
JS objects are great tools to use for tracking unique items. If you start with an empty object, you can incrementally add keys/values. If the object already has a key for a given item, you can set it to some known value that is use used to indicate a non-unique item.
You could then loop over the object and push the unique items to an array.
var itemsObj = {};
var itemsList = [];
x = [{"id":"item1","val":"foo"},
{"id":"item2","val":"bar"},
{"id":"item1","val":"baz"},
{"id":"item1","val":"bez"}];
for (var i = 0; i < x.length; i++) {
var item = x[i];
if (itemsObj[item.id]) {
itemsObj[item.id] = "dupe";
}
else {
itemsObj[item.id] = item;
}
}
for (var myKey in itemsObj) {
if (itemsObj[myKey] !== "dupe") {
itemsList.push(itemsObj[myKey]);
}
}
console.log(itemsList);
See a working example here: https://jsbin.com/qucuso
If you want a list of items that contain only the first instance of an id, you can do this:
var itemsObj = {};
var itemsList = [];
x = [{"id":"item1","val":"foo"},
{"id":"item2","val":"bar"},
{"id":"item1","val":"baz"},
{"id":"item1","val":"bez"}];
for (var i = 0; i < x.length; i++) {
var item = x[i];
if (!itemsObj[item.id]) {
itemsObj[item.id] = item;
itemsList.push(item);
}
}
console.log(itemsList);
This is late but I did something like the following:
let MyArray = [];
MyArray._PushAndRejectDuplicate = function(el) {
if (this.indexOf(el) == -1) this.push(el)
else return;
}
MyArray._PushAndRejectDuplicate(1); // [1]
MyArray._PushAndRejectDuplicate(2); // [1,2]
MyArray._PushAndRejectDuplicate(1); // [1,2]
This is how I would do it in pure javascript.
var x = [{"id":"item1","val":"Items"},{"id":"item1","val":"Items"},{"id":"item1","val":"Items"}];
function unique(arr, comparator) {
var uniqueArr = [];
for (var i in arr) {
var found = false;
for (var j in uniqueArr) {
if (comparator instanceof Function) {
if (comparator.call(null, arr[i], uniqueArr[j])) {
found = true;
break;
}
} else {
if (arr[i] == uniqueArr[j]) {
found = true;
break;
}
}
}
if (!found) {
uniqueArr.push(arr[i]);
}
}
return uniqueArr;
};
u = unique(x, function(a,b){ return a.id == b.id; });
console.log(u);
y = [ 1,1,2,3,4,5,5,6,1];
console.log(unique(y));
Create a very readable solution with lodash.
x = _.unionBy(x, [new Item(clickId, obj)], 'id');
let x = [{id:item1,data:value},{id:item2,data:value},{id:item3,data:value}]
let newEle = {id:newItem,data:value}
let prev = x.filter(ele=>{if(ele.id!=new.id)return ele);
newArr = [...prev,newEle]

Select array index by object value

If I have an array like this:
var array = [{ID:1,value:'test1'},
{ID:3,value:'test3'},
{ID:2,value:'test2'}]
I want to select an index by the ID.
i.e, I want to somehow select ID:3, and get {ID:3,value:'test3'}.
What is the fastest and most lightweight way to do this?
Use array.filter:
var results = array.filter(function(x) { return x.ID == 3 });
It returns an array, so to get the object itself, you'd need [0] (if you're sure the object exists):
var result = array.filter(function(x) { return x.ID == 3 })[0];
Or else some kind of helper function:
function getById(id) {
var results = array.filter(function(x) { return x.ID == id });
return (results.length > 0 ? results[0] : null);
}
var result = getById(3);
With lodash you can use find with pluck-style input:
_.find(result, {ID: 3})
Using filter is not the fastest way because filter will always iterate through the entire array even if element being search for is the first element. This can perform poorly on larger arrays.
If you are looking for fastest way, simply looping through until the element is found might be best option. Something like below.
var findElement = function (array, inputId) {
for (var i = array.length - 1; i >= 0; i--) {
if (array[i].ID === inputId) {
return array[i];
}
}
};
findElement(array, 3);
I would go for something like this:
function arrayObjectIndexOf(myArray, property, searchTerm) {
for (var i = 0, len = myArray.length; i < len; i++) {
if (myArray[i].property === searchTerm)
return myArray[i];
}
return -1;
}
In your case you should do:
arrayObjectIndexOf(array, id, 3);
var indexBy = function(array, property) {
var results = {};
(array||[]).forEach(function(object) {
results[object[property]] = object;
});
return results
};
which lets you var indexed = indexBy(array, "ID");

Get object's index from an array [duplicate]

This question already has answers here:
Getting index of an array's element based on its properties
(7 answers)
Closed 9 years ago.
My array looks something like this:
var someArray =
[
{ id: 'someID', name: 'someName', title: 'someTitle' },
{ id: 'anotherID', name: 'anotherName', title: 'anotherTitle' },
{ id: 'otherID', name: 'otherName', title: 'otherTitle' }
];
I want to get index reference of an object that who's id === 'anotherID' in reference with someArray
I know that I can use $.grep() to return an object:
var resultArray = $.grep(columns, function(e){return e.id === 'anotherID'});
resultArray will return an array of objects that match the condition of anonymous function, but it will not return an index of that object in someArray
I am looking for JavaScript/Jquery solution.
Thank you.
A simple for:
var elementIndex = false;
for ( var index = 0, length = someArray.length; index < length; index++ ) {
if ( someArray[index].id === 'anotherID' ) {
elementIndex = index;
break;
}
}
if ( elementIndex !== false ) {
console.log(elementIndex);
}
The easiest way is going to be to write your own function (unless you have access to the built-in indexOf method and it works for you:
var indexOf = function(array, predicate) {
for(var i = 0; i < array.length; i++) {
if(predicate(array[i])) {
return i;
}
}
return -1;
}
Which you could then call like:
var index = indexOf(someArray, function(e){ return e.id === 'anotherID'; });
.reduce() is not supported by IE8
One liner using reduce:
someArray.reduce(function(p,c,i){return c.id=='anotherID'?i:p},-1);
Using jQuery's $.each:
var posIndex = '';
$.each(someArray, function(i){
if(someArray[i].id === 'anotherID'){
posIndex = i;
return false; /*Stop iterating once found. Tip from Felix Kling*/
}
});
See example fiddle (upper code part).
As you see, using jQuery is alot easier than normal JS for.
This answer considers that each id is really an identifier (a.k.a. unique) or else posIndex will return the position of the last object which has anotherID.
Update
Added 'Felix Kling' tip of return false;. Now it will return the first match only. If have more than one id with same value, use below. Thanks Felix.
If you think that might have multiple equal ids, I suggest that posIndex becomes an array and then you read the array later. Example:
var posIndexArray = [];
$.each(someOtherArray, function (i) {
if (someOtherArray[i].id === 'anotherID') {
posIndexArray.push(i);
}
});
And posIndexArray will be a comma separated list of indexes you can then use $.each on it to do whatever you want with the indexes.
See example fiddle (lower code part).
You can achieve this pretty easily using either plain ol' JavaScript or jQuery if you so choose!
le vanilla
function getById(id, objects) {
for (var i = 0, length = objects.length; i < length; i++) {
if (objects[i].id === id) {
return i;
}
}
return -1;
}
jQueryyy
function getById(id, objects) {
var index = -1;
$.each(objects, function(i) {
if (this.id === id) {
index = i;
return false;
}
});
return index;
}
From that point, you could just call your little handy-dandy helper function and check that you got a good index.
var index = getById('otherId', someArray);
if (~index) {
doSomethingAwesome(someArray[index]);
}

Check if duplicate array pairs exist using underscore only

I am wondering how I can check if a duplicate pair of values in an array exist as part of a larger array in javascript. You can see there is a duplicate pair of [1,2] - so the function should just return true. i.e
var arr = [[1,2], [3,4], [5,6], [7,8], [9,10], [11,12], [13,14], [1,2]]
I have tried using this logic which gives me a clean array and a "true"
var unique = [];
var done = []; var dup = false;
for(var x = 0; x < arr.length; x++) {
var myStr = arr[x].toString();
if(done.indexOf(myStr) != -1) {
// val already exist, ignore
dup = true;
continue;
}
done.push(myStr);
unique.push(arr[x]);
}
But I was wondering if there is something more elegant using Underscore ?
The shortest way would be to use _.uniq and JSON.stringify:
function unique(arr) {
return _.uniq(arr, JSON.stringify).length === arr.length;
}
But that doesn't short-circuit, so it's somewhat slow compared to the other ways you could do it. Tomalak's second function should be faster.
Well, uniq seems like a good fit
function containsDuplicates(arr) {
return arr.length !== _.uniq(arr, function (item) { return item.toString(); }).length;
}
You should use Blender's version of this function. It's shorter and safer.
BTW, your code should look more like this:
function containsDuplicates(arr) {
var index = {}, i, str;
for(i = 0; i < arr.length; i++) {
// you could use arr[i].toString() here, but JSON.stringify()
// is a lot safer because it cannot create ambiguous output.
str = JSON.stringify(arr[i]);
if (index.hasOwnProperty(str)) {
return true;
} else {
index[str] = true;
}
}
return false;
}
Note that this is probably more efficient than the underscore one-liner.
Although stringify is the answer most of the time, it still has its issues, for example {"x":1,"y":2} and {"y":2,"x":1} are considered different. If you need a 100% accurate comparison, there's no other way as to store already processed objects and deep compare them (luckily, underscore provides an utility for this).
uniq2 = function(xs) {
return _.reduce(xs, function(result, x) {
if(!_.any(result, _.partial(_.isEqual, x)))
result.push(x);
return result;
}, []);
}
Test:
var arr = [[1,2], [3,4], "1,2", "[1,2]", [1,2], {x:1,y:2}, {y:2,x:1}]
console.log(uniq2(arr))
// [[1,2],[3,4],"1,2","[1,2]",{"x":1,"y":2}]
This is going to be quadratic in the worst case, but there's no other way.

Get next key-value pair in an object

Given a key, I want to find the next property in an object. I can not rely on the keys to be ordered or sequential (they're uuids). Please see below for trivial example of what I want:
var db = {
a: 1,
b: 2,
c: 3
}
var next = function(db, key) {
// ???
}
next(db, 'a'); // I want 2
next(db, 'b'); // I want 3
I also want a prev() function, but I'm sure it will be the same solution.
This seems like such a trivial problem but I can't for the life of me figure out how to do it.
Happy for the solution to use underscore.js or be written in coffeescript :)
ts / es6 version. I simply get the keys from the storeObject, look for the next Index.
let keys = Object.keys(storeObject);
let nextIndex = keys.indexOf(theCurrentItem) +1;
let nextItem = keys[nextIndex];
The correct answer is: you can't do that, as objects are unordered as per ECMAScript's spec.
I'd recommend that you use an ordered structure, like an array, for the purpose of the problem:
var db = [
{key: 'a', value: 1},
{key: 'b', value: 2},
{key: 'c', value: 3}
];
Then the next function can be something like:
var next = function(db, key) {
for (var i = 0; i < db.length; i++) {
if (db[i].key === key) {
return db[i + 1] && db[i + 1].value;
}
}
};
In case key does not exist on db or it was the last one, next returns undefined. if you're never going to ask for the next of the last item, you can simplify that function by removing the ternary && operator and returning db[i + 1].value directly.
You can also use some of Underscore.js utility methods to make next simpler:
var next = function(db, key) {
var i = _.pluck(db, 'key').indexOf(key);
return i !== -1 && db[i + 1] && db[i + 1].value;
};
(in this case next could return false sometimes... but it's still a falsy value :))
Now, a more pragmatic answer could be that, as most browsers will respect the order in which an object was initialized when iterating it, you can just iterate it with a for in loop as the other answers suggest. I'd recommend using Object.keys to simplify the job of iterating over the array:
// Assuming that db is an object as defined in the question.
var next = function(db, key) {
var keys = Object.keys(db)
, i = keys.indexOf(key);
return i !== -1 && keys[i + 1] && db[keys[i + 1]];
};
function next(db, key){
var found = 0;
for(var k in db){
if(found){ return db[k]; }
if(k == key){ found = 1; }
}
}
An immediate solution to this would be to store data in an array and use the object to simply store the index in the array at which an object exists.
var db = {
data: [1, 2, 3],
index: {
a: 0,
b: 1,
c: 2
}
};
function next(db, key) {
var next = db.index[key] + 1;
if (next >= db.data.length) {
return null;
}
return db.data[next];
}
function prev(db, key) {
var next = db.index[key] - 1;
if (next < 0) {
return null;
}
return db.data[next];
}
function add(db, key, value) {
db.index[key] = db.data.push(value) - 1;
}
function remove(db, key) {
var index = db.index[key], x, temp;
if (index !== undefined) {
delete db.index[key];
db.data.splice(index, 1);
// Update indices of any elements after the removed element
for (x in db.index) {
temp = db.index[x];
if (temp > index) {
db.index[x] = temp - 1;
}
}
}
}
The basic idea is to use an ordered structure, in this case the array, to hold the data in a sequential manner. In this case, next and prev are both constant time, add is amortized constant time, and delete is O(N).
The ordering of keys isn't guaranteed by the ECMA standard, so for/in doesn't need to be in the order keys were added (though in practice, that does tend to be the common implementation). In this solution, I use an array to explicitly keep track of insert order.
Edit: I overlooked a deletion issue earlier with splice. The index would become incorrect for all values after the spliced value for a remove. The fix doesn't impact the running time complexity of the operation. A faster version with fewer removes could let the array become sparse and instead of splicing, simply set the index to null to free any reference stored there. This would lower the remove operation to O(1).
function remove(db, key) {
var index = db.index[key];
if (index !== undefined) {
delete db.index[key];
db.data[index] = null;
}
}
Using undercore.js, you can take the keys of an object and do the trick. But I'm not sure if the key-value pairs are ordered in any way to begin with:
var next = function(db, key) {
var keys = _.keys(db);
var index = _.indexOf(keys, key);
if(index+1<keys.length){
return db[keys[index+1]];
}else{
return null;
}
}
jsFiddle: http://jsfiddle.net/QWhN2/
I landed here in 2021 so i'll post an Es6 solution.
A simple solution that let you navigate the object given a starting key:
const navObj = (obj, currentKey, direction) => {
return Object.values(obj)[Object.keys(obj).indexOf(currentKey) + direction];
};
const db = {
a: 1,
b: 2,
c: 3
};
console.log(navObj(db, 'a', 1));
console.log(navObj(db, 'a', 2));
console.log(navObj(db, 'b', -1));

Categories