More efficient array search - javascript

I'd like to pull a particular object from an array of objects based on a unique property of that object (ie, a key).
In the following, I'm searching for an element in 'arr' where the key is 8.
var myElement = arr.filter(function(element) {
return element.key === 8;
});
This works, but every time this runs, it will iterate through all elements in the array, even after the correct element has been found. For example, if it finds myElement at index 4, but there are 100 elements in the array, the snippet is running 25x more than it needs to.
Is there a more efficient way, or a way to terminate filter() when it has found myElement?
I feel like I've missed something obvious...

You actually want a find function that returns when the first occurrence is found. You can use Array.prototype.find if you are on ECMAScript 6 spec or you can implement a simple find like:
function find8(arr) {
// type checks skipped for brevity
var i = 0,
j = arr.length;
for (; i < j; i++) {
if (arr[i].key === 8) {
return arr[i];
}
}
throw new Error('Not found');
}
If you want a more generic solution that accepts predicates (i.e. functions that return a boolean value), you can write a more generic function like:
function find(arr, predicate) {
// Again, type checks skipped for brevity
var i = 0,
j = arr.length;
for (; i < j; i++) {
// Note the condition is now a call to the predicate function
if (predicate(arr[i])) {
return arr[i];
}
}
throw new Error('Not found');
}

for (var i=0; i<arr.length; i++) {
if (arr[i].key === 8) {
console.log('FOUND ITEM AT KEY '+8);
break;
}
}

It sounds like you'd be better off with something like this:
function findByKey(items, key) {
for (var index = 0; index < items.length; index++) {
var item = items[index];
if (item.key === key) {
return item;
}
}
return null;
}
Note that if your list is sorted, you can improve upon that by doing a binary-search instead. Though personally I'd suggest hashing (assuming that the same key isn't used twice). Something like:
var array = [/* your data here... */];
var map = {};
//do this once; or once each time your array changes
for (var index = 0; index < array.length; index++) {
var item = array[index];
map[item.key] = item;
}
//now find things by doing this
var itemWithKey8 = map[8];

I'd handle this with a simple for loop (used a function for generic re-use):
function findObject(arr, cond)
for (i = 0; i < arr.length; i++) {
if (cond(arr[i]){
return arr[i];
}
}
}
// now call fincObject(myArray, function(element){element.key === 8})
Or, if you know you are going to do this many times, create a mapping which should be a lot faster:
function makeMapping(arr, keyFunc){
var mapping = {};
for (var i = 0; i < arr.length; i++) {
mapping[keyFunc(arr[i])] = arr[i];
}
return mapping;
}
This returns an object mapping 8 to the object with key.id === 8. Your keyFunc would be:
function keyFunc(element){
return element.key;
}

Why reinvent the wheel? There are a lot of similar answers here, so I'll share a different approach--by far my favorite approach. There is an excellent library called linq.js (both standalone and jQuery plugin versions) which makes searching, filtering, sorting, etc. a breeze.
var myElement = Enumerable.From(arr).FirstOrDefault(null, function(element) {
return element.key === 8;
});
In the above example, the first element that matches the conditions is returned. If nothing is found, null is returned (the first parameter is the default value to return).

Related

JavaScript - Writing indexOf and each

I'm trying to rewrite _.each and _.indexOf and it is throwing me for a loop.
My each function takes either an object or an array and passes tests set up.
_['each'] = function(collection, iterator) {
if (Array.isArray(collection) === false && typeof(collection) === 'object') {
var values = Object.values(collection);
var keys = Object.keys(collection);
for (let i = 0; i < values.length; i++) {
iterator(values[i], keys[i], collection, i);
}
} else {
for (let i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
}
}
So I'm assuming this code is alright since it passes a preset test, but I'm not even sure about that. My question is, how would I write an indexOf() function that also uses the each() function? Each will run a function for every element and won't break, right? And I can't access the index via collection[i] because i is undefined in indexOf's scope. What am I missing?
pseudo code, check if you need the full javascript code :
indexOf = function(element, array){
// found index
index = -1;
// for each element, compare element, store index if found
each array (function(e, i){
// use cmp function to deep compare object/array as values
if(e === element)
{
// remove condition for lastIndexOf
if(index == -1)
{
index = i;
}
}
});
// return found index
return index;
}

Getting wrong result with Number.isInteger() method, javascript

I've been trying the Number.isInteger() method on chrome console.
and after doing a for loop and checking the result with the console.log(arr); I'm getting an array with only one value of 1. like this [1];
var arr = [1,2,3,'some','string'];
for (var i = 0; i < arr.length; i++) {
if (Number.isInteger(arr[i])) {
arr.splice(arr.indexOf(arr[i], 1));
}
}
Any one have an idea, if I'm doing it wrong or something. thanks for help.
You have a major problem, you are continually removing items from the array while looping through it. You have to go back one step (i--) every time an item is removed.
var arr = [1,2,3,'some','string'];
for (var i = 0; i < arr.length; i++) {
if (!isNaN(arr[i])) { // isNaN return true if it's not a valid number, so we have to inverse the test
arr.splice(i, 1); // if it's a valid number remove the element at the index i (no need to search for the index using indexOf, we already have it its i)
i--; // if we remove an element, we have to go back one step (or we will leave one item behind every time we remove another)
}
}
console.log(arr);
You could use typeof instead:
var arr = [1,2,3,'some','string'];
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] == 'number') {
arr.splice(arr.indexOf(arr[i], 1));
}
}
The `.splice()' method makes changes to the array itself.
So for each iteration of the array, you are changing it fundamentally.
If you want the array to include only integers:
var arr = [1,2,3,'some','string'];
var newArray = [];
arr.forEach(function(element) {
if (Number.isInteger(element)){
newArray.push(element);
}
});
console.log(newArray);

How to add objects into an array without repeating value of one of the properties of objects in JavaScript?

I am trying to insert data into an array. The data is of object form with 2 properties name and value. I am retrieving the data from a different array object with a completely different structure.
The new array that I am creating is as follow:
newarray = [Object, Object, Object]
where Object = {name: "abc", value: "12"}
This is what I am doing for my purpose:
var newarray = [];
for (var i = 0; i < oldarray.length; i++) {
a = newarray.indexOf(oldarray[i].studentname);
if (a == -1) {
newarray.push({
name: oldarray[i].studentname,
value: oldarray[i].marks
})
}
}
I don't want the student name to repeat so I have tried to use indexOf to check the occurrence of the name in the array. But I am not doing it correctly. What am I doing wrong?
var newarray= [];
for(var i=0; i<oldarray.length; i++)
{
var found = false;
for (var j=0; j<newarray.length; j++) {
if (oldarray[i].studentname == newarray[j].studentname) {
found = true;
break;
}
}
if(!found)
{
newarray.push({
name : oldarray[i].studentname,
value :oldarray[i].marks
});
}
}
This code:
a = newarray.indexOf(oldarray[i].studentname);
if (a == -1)
looks to see if the student's name is an entry in the array. But the entries you're putting in the array aren't just namnes, they're objects.
If you want to find if an object with the same name is in the array, you can use Array#some:
if (!newarray.some(function(entry) { return entry.name == oldarray[i].studentname; })) {
// It's not there, add it
}
Array#some calls its callback for each entry. If the callback returns a truthy value for any entry, some stops and returns true; if the callback never returns a truthy value (or the array is empty), some returns false.
It's a bit more concise with ES2015, if you're using ES2015:
if (!newarray.some(entry => entry.name == oldarray[i].studentname)) {
// It's not there, add it
}
== will require that the names be in the same capitalization, of course. If that may not be true, you might want to make both sides lower (or upper) case first.
I don't see a declared anywhere, make sure you're not falling prey to The Horror of Implicit Globals.
var newarray = [];
for (var i = 0; i < oldarray.length; i++) {
a = findWithAttr(newarray, "studentname", oldarray[i].studentname);
if (a == undefined) {
newarray.push({
name: oldarray[i].studentname,
value: oldarray[i].marks
})
}
}
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
}

How to prototype a duplicate prototype method for Array in javascript

I'm trying to implement a duplicate method to the js Array prototype which concats a duplicate of the array to itself like so:
[11,22,3,34,5,26,7,8,9].duplicate(); // [11,22,3,34,5,26,7,8,9,11,22,3,34,5,26,7,8,9]
Here's what I have, but it causes the browser to crash:
var array = [11,22,3,34,5,26,7,8,9];
Array.prototype.duplicate = function() {
var j = this.length;
for(var i = 0; i < this.length; i++) {
this[j] = this[i];
j++;
}
return this;
}
I'm trying to do this using native JS as practice for iterations and algorithms so I'm trying to avoid built-in methods, if possible, so that I can get a clearer understanding of how things are being moved around.
Any ideas on why it is crashing and how I can optimize it?
The code inside the loop changes the length of the array, so it will just keep growing and you will never reach the end of it. Get the initial length of the array in a variable and use in the loop condition. You can use that as offset for the target index also instead of another counter:
var array = [11,22,3,34,5,26,7,8,9];
Array.prototype.duplicate = function() {
var len = this.length;
for (var i = 0; i < len; i++) {
this[len + i] = this[i];
}
return this;
}
The length of the array is increasing with each element added so you can not use this as a terminator. Try this.
var j = this.length;
for(var i = 0; i < j; i++) {
this[i+j] = this[i];
}
Here's simplest code
Array.prototype.duplicate = function () {
var array = this;
return array.concat(array);
};
Using Spread syntax;
In a method, this refers to the owner object.
Array.prototype.duplicate = function () {
return [...this, ...this]
};
let array = [1,2,3,4,5];
Array.prototype.duplicate = function () {
return [...this, ...this]
};
console.log(array.duplicate());

Javascript: loop through all records within an object with an irregular key

UPDATED
I've got an object var myObject = {};
I build it using a key like so:
myObject[key] = {
name: ...
};
So imagine I have created three records:
myObject[13] = {...};
myObject[281] = {...};
myObject[76] = {...};
I now want to loop through the object as if it were an array.
var i,
length = myObject.length; // ?? Problem here 'cos it ain't an array
for (i = 0; i < length; i += 1) {
????
};
How can I refer to the three elements at ????? I don't know the numbers 13, 281 & 76, and myObject[0] is not going to find the first record.
Thanks.
You shouldn't be using an array if your keys aren't consecutive. Use an object instead:
var myObject = {};
To loop over an object's keys, you use the for..in syntax:
for (var key in myObject) {
var value = myObject[key];
}
To make your current code work, you'd have to loop over all of the keys and check to see if they have values:
for (var i = 0; i < myArray.length; i++) {
var value = myArray[i];
if (typeof value === 'undefined') continue;
console.log(key, ' -> ', value)
}
If you're using a modern JavaScript engine:
myArray.forEach(function(val, key) {
// val = the value
// key = the index
});
And that's it. To get its size (3, in this case)...
var size = myArray.reduce(function(prev) {
return ++prev;
}, 0);
If you want this to work in older browsers, you'll need to do something else. The easiest solution would probably be to implement something like this yourself:
for (var i = 0; i < myArray.length; i ++) {
var val = myArray[i];
if (typeof val !== "undefined") {
console.log(val);
}
}
To get its size, it's a similar endeavor (that you could combine with the above method if you're doing both):
var size = 0;
for (var i = 0; i < myArray.length; i ++) {
if (typeof myArray[i] !== "undefined")
size ++;
}
If you want to use forEach and reduce like the above, you'll need to find a shim (basically, you'll need to implement those functions yourself). I'd recommend the larger of the two forEach shims on this page and the reduce shim on this page, or use a library that shims them all.

Categories