I am trying to come up with a single function that returns the object with the longest length array within an array.
Here is the question:
Write a function that lists all the Orcs that have the most the weapons.
Example:
var orcs = [{
name: 'Orgoth',
strength: 9001,
weapons: ['Bone ax', 'Mace of Strength']
}, {
name: 'Blaroguhh',
strength: 500,
weapons: ['Cheeseburger', 'Spear of the Hut']
}, {
name: 'Mark',
strength: 543,
weapons: ['Ax of Defense', 'Dagger', 'Sword']
}]
getMostWeapons(orcs);
// => {name: 'Mark', strength: 543, weapons: ['Ax of Defense', 'Dagger', 'Sword' ]}
And this is what I have so far:
function getMostWeapons(orcs) {
var length = 0;
return orcs.filter(function (obj) {
return obj.filter(function (val) {
if (val.length > length) {
return (length = val.length);
}
});
});
}
.filter is used to return all the array elements that match a criteria. Since you don't know the maximum length until you've gone through all the orcs, you can't use it in one pass to find the orc to return.
Just use an ordinary loop that compares the length of weapons to the longest seen so far. If it's longer, replace the longest with this one.
function getMostWeapons(orcs) {
var longest = 0;
var longestOrcs = [];
orcs.forEach(function(orc) {
if (orc.weapons.length > longest) {
longestOrcs = [orc];
longest = orc.weapons.length;
} else if (orc.weapons.length == longest) {
longestOrcs.push(orc);
}
});
return longestOrcs;
}
.filter() isn't really what you want here because you wouldn't know what you were filtering for without first making a pass to see what the max length was. .reduce() could be used (for side effects), but you aren't really accumulating a single value here like .reduce() is built for. So, it makes sense to just use .forEach() with a couple parent-scoped variables to keep track of our state.
To return all objects that have the max length, you can do this (in a snippet you can run to see the results). This returns an array of all objects that have the maximum length.
var orcs = [{
name: 'Orgoth',
strength: 9001,
weapons: ['Bone ax', 'Mace of Strength']
}, {
name: 'Blaroguhh',
strength: 500,
weapons: ['Cheeseburger', 'Spear of the Hut']
}, {
name: 'Mark',
strength: 543,
weapons: ['Ax of Defense', 'Dagger', 'Sword']
}];
function getMostWeapons(o) {
var max = 0, maxObj = [];
o.forEach(function(item) {
if (item.weapons.length > max) {
max = item.weapons.length;
maxObj = [item];
} else if (item.weapons.length === max) {
maxObj.push(item);
}
});
return maxObj;
}
var max = getMostWeapons(orcs);
log(max);
function log(x) {
document.write(JSON.stringify(x));
}
You can use filter although it's nearly unreadable and cannot be called "good coding" with a clear conscious but when others can use forEach with a function instead of a simple loop without any good reason ... ;-)
JSON.stringify(
orcs.sort(function(a,b){
return b.weapons.length - a.weapons.length;
}).filter(function(value,index,array){
return value.weapons.length == array[0].weapons.length;
})
)
I'd separate into two functions, one to find the max, and one to find all orc with that max:
function getMostWeapons(allOrcs) {
return allOrcs.reduce((max, currentOrc) =>
Math.max(max, currentOrc.weapons.length), 0);
}
function getOrcsWithMostWeapons(allOrcs) {
let maxWeapons = getMostWeapons(allOrcs);
return allOrcs.filter(orc => orc.weapons.length === maxWeapons);
}
Note that this will return an array even if there's only one orc. I find it best to always return the same type.
Try like this
var length=Math.max.apply(Math,orcs.map(function(o){return o.weapons.length;}));
var obj=orcs.find(function(x){ return x.weapons.length == length;}); // single object
var objList=orcs.filter(function(x){ return x.weapons.length == length;}); // multple
console.log(obj);
console.log(objList);
JSFIDDLE
Late answer but good if anyone comes here looking for a similar answer, I took the current answer and converted it to use for in, a little more generic too
function getLargest(o) {
var max = 0, maxObj = [];
for ( var item in o)
{
if (o[item].length > max) {
max = o[item].length;
maxObj = [item];
} else if (o[item].length === max) {
maxObj.push(item);
}
};
return maxObj;
}
A simple loop with a length variable would probably be the best way
function getMostWeapons(orcs) {
var len = 0; //longest
for( x in orcs ){ //simple loop
len = len < orcs[x].weapons.length ? orcs[x].weapons.length : len;
}
}
Related
I know we can match array values with indexOf in JavaScript. If it matches it wont return -1.
var test = [
1, 2, 3
]
// Returns 2
test.indexOf(3);
Is there a way to match objects? For example?
var test = [
{
name: 'Josh'
}
]
// Would ideally return 0, but of course it's -1.
test.indexOf({ name: 'Josh' });
Since the two objects are distinct (though perhaps equivalent), you can't use indexOf.
You can use findIndex with a callback, and handle the matching based on the properties you want. For instance, to match on all enumerable props:
var target = {name: 'Josh'};
var targetKeys = Object.keys(target);
var index = test.findIndex(function(entry) {
var keys = Object.keys(entry);
return keys.length == targetKeys.length && keys.every(function(key) {
return target.hasOwnProperty(key) && entry[key] === target[key];
});
});
Example:
var test = [
{
name: 'Josh'
}
];
var target = {name: 'Josh'};
var targetKeys = Object.keys(target);
var index = test.findIndex(function(entry) {
var keys = Object.keys(entry);
return keys.length == targetKeys.length && keys.every(function(key) {
return target.hasOwnProperty(key) && entry[key] === target[key];
});
});
console.log(index);
Note that findIndex was added in ES2015, but is fully polyfillable.
Nope, you can't and the explanation is simple. Despite you use the same object literal, two different objects are created. So test would have another reference for the mentioned object if you compare it with the reference you are looking for in indexOf.
This is kind of custom indexOf function. The code just iterates through the items in the object's array and finds the name property of each and then tests for the name you're looking for. Testing for 'Josh' returns 0 and testing for 'Kate' returns 1. Testing for 'Jim' returns -1.
var test = [
{
name: 'Josh'
},
{
name: 'Kate'
}
]
myIndexOf('Kate')
function myIndexOf(name) {
testName = name;
for (var i = 0; i < test.length; i++) {
if(test[i].hasOwnProperty('name')) {
if(test[i].name === testName) {
console.log('name: ' + test[i].name + ' index: ' + i);
return i;
}
}
}
return -1;
}
You can loop on array and then look for what you want
var test = [{ name: 'Josh' }]
const Myname = test.map((item) => { return item.name; }).indexOf("Josh")
I have an array of objects which I would like to filter and divide into groups according to a conditional. The predicament comes because I have more than one conditional, and would like the array to be divided into several arrays. First array matching the first conditional, second array matching second conditional, ... , and the last array containing all objects not matching any conditional.
The first solution that came to mind to this problem was in the form of several .filter functions...
var array = [{
name: 'X',
age: 18
}, {
name: 'Y',
age: 18
}, {
name: 'Z',
age: 20
}, {
name: 'M',
age: 20
}, {
name: 'W',
age: 5
}, {
name: 'W',
age: 10
}];
//objects with age 18
var matchedConditional1 = array.filter(function(x){
return x.age === 18;
});
//objects with age 20
var matchedConditional2 = array.filter(function(x){
return x.age === 20;
});
//objects with neither age 18 or 20
var matchedNoConditional = array.filter(function(x){
return (x.age !== 18 && x.age !== 20);
});
but that seemed redundant and not reusable at all.
So I modified the function on Brendan's answer, and got this.
Array.prototype.group = function(f) {
var matchedFirst = [],
matchedSecond = [],
unmatched = [],
i = 0,
l = this.length;
for (; i < l; i++) {
if (f.call(this, this[i], i)[0]) {
matchedFirst.push(this[i]);
} else if (f.call(this, this[i], i)[1]) {
matchedSecond.push(this[i]);
} else {
unmatched.push(this[i]);
}
}
return [matchedFirst, matchedSecond, unmatched];
};
var filteredArray = array.group(function(x){
return [x.age === 18, x.age === 20];
});
This method returns an array with 3 arrays. The first one containing all objects matching the first conditional, second one with objects matching the second conditional, and the last one with objects not matching any of the two.
The problem with this method is that it is limited to two conditionals and hence only three groups of objects. This method actually works for my particular situation for I only have two conditionals, but is not reusable in situations that require more than two.
I would like to be able to give as many conditionals as I want and receiving that amount of arrays plus an extra array with objects not belonging to any group.
Ps. The input and output don't need to be arrays, but I thought that makes more sense. The method doesn't have to be modeled after .filter, it very well could be a .map function or even a .reduce. Any suggestion is appreciated.
Edit: as suggested by #slebetman, it would be great if the answer allowed for code composability.
We'll use findIndex to find the index of the condition which matches, and put the element in the corresponding array element of the output:
function makeGrouper(...conditions) {
return function(array) {
// Make an array of empty arrays for each condition, plus one.
var results = conditions.map(_ => []).concat([]);
array.forEach(elt => {
var condition = conditions.findIndex(condition => condition(elt));
if (condition === -1) condition = conditions.length;
results[condition].push(elt);
});
return results;
};
}
Or, if you are a fan of reduce:
function makeGrouper(...conditions) {
return function(array) {
return array.reduce((results, elt) => {
var condition = conditions.findIndex(condition => condition(elt));
if (condition === -1) condition = conditions.length;
results[condition].push(elt);
return results;
}, conditions.map(_ => []).concat([])));
};
}
Usage:
const grouper = makeGrouper(
elt => elt.age === 18,
elt => elt.age === 20
);
console.log(grouper(data));
This solution involves defining a function to which you provide the various filters, which returns a function which you can then use to do the actual grouping.
Try something like this:
Array.prototype.groups = function(...conditions) {
return this.reduce(
(groups, entry) => {
let indices = [];
conditions.forEach((cond, i) => {
if (cond(entry)) indices.push(i);
});
if (indices.length === 0) groups[groups.length - 1].push(entry);
else indices.forEach(i => groups[i].push(entry));
return groups
},
Array.apply(null, { length: conditions.length + 1})
.map(e => [])
);
}
In this solution if an entry matches more than one condition it will appear in corresponding number of groups.
Usage example: array.groups(x => x.name === 'X', x => x.age === 18);
The last element in a final array - unmatched entries.
I desperately need to implement client side sorting that emulates sorting through our tastypie api, which can take multiple fields and return sorted data. So if for example I have data like:
arr = [
{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment', budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 500 },
...
etc.
]
and given columns to sort e.g.:['name', '-number_of_reqs'] it should sort by name (ascending) and number_of_reqs (descending). I can't get my head around this,
first of all it has to be "natural sort", it supposed to be fairly easy to get if we're talking about sorting a single column, but I need to be able to sort in multiple.
Also I'm not sure why I'm getting different results (from the way how api does it) when using lodash's _.sortBy? Is _.sortBy not "natural" or it's our api broken?
Also I was looking for an elegant solution. Just recently started using Ramdajs, it's so freaking awesome. I bet it would be easier to build sorting I need using that? I've tried, still can't get it right. Little help?
upd:
I found this and using it with Ramda like this:
fn = R.compose(R.sort(naturalSort), R.pluck("name"))
fn(arr)
seems to work for flat array, yet I still need to find a way to apply it for multiple fields in my array
fn = R.compose(R.sort(naturalSort), R.pluck("name"))
seems to be working
Really? I would expect that to return a sorted array of names, not sort an array of objects by their name property.
Using sortBy unfortunately doesn't let us supply a custom comparison function (required for natural sort), and combining multiple columns in a single value that compares consistently might be possible but is cumbersome.
I still don't know how to do it for multiple fields
Functional programming can do a lot here, unfortunately Ramda isn't really equipped with useful functions for comparators (except R.comparator). We need three additional helpers:
on (like the one from Haskell), which takes an a -> b transformation and a b -> b -> Number comparator function to yield a comparator on two as. We can create it with Ramda like this:
var on = R.curry(function(map, cmp) {
return R.useWith(cmp, map, map);
return R.useWith(cmp, [map, map]); // since Ramda >0.18
});
or - just like ||, but on numbers not limited to booleans like R.or. This can be used to chain two comparators together, with the second only being invoked if the first yields 0 (equality). Alternatively, a library like thenBy could be used for this. But let's define it ourselves:
var or = R.curry(function(fst, snd, a, b) {
return fst(a, b) || snd(a, b);
});
negate - a function that inverses a comparison:
function negate(cmp) {
return R.compose(R.multiply(-1), cmp);
}
Now, equipped with these we only need our comparison functions (that natural sort is an adapted version of the one you found, see also Sort Array Elements (string with numbers), natural sort for more):
var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
function naturalCompare(a, b) {
var aa = String(a).split(NUMBER_GROUPS),
bb = String(b).split(NUMBER_GROUPS),
min = Math.min(aa.length, bb.length);
for (var i = 0; i < min; i++) {
var x = aa[i].toLowerCase(),
y = bb[i].toLowerCase();
if (x < y) return -1;
if (x > y) return 1;
i++;
if (i >= min) break;
var z = parseFloat(aa[i]) - parseFloat(bb[i]);
if (z != 0) return z;
}
return aa.length - bb.length;
}
function stringCompare(a, b) {
a = String(a); b = String(b);
return +(a>b)||-(a<b);
}
function numberCompare(a, b) {
return a-b;
}
And now we can compose exactly the comparison on objects that you want:
fn = R.sort(or(on(R.prop("name"), naturalCompare),
on(R.prop("number_of_reqs"), negate(numberCompare))));
fn(arr)
I think this works.
var arr = [
{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment', budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 5000 },
{ name: 'STS 10', budget: 50000, number_of_reqs: 500 }
];
var columns = ['name', 'number_of_reqs'];
var NUMBER_GROUPS = /(-?\d*\.?\d+)/g;
var naturalSort = function (a, b, columnname) {
var a_field1 = a[columnname],
b_field1 = b[columnname],
aa = String(a_field1).split(NUMBER_GROUPS),
bb = String(b_field1).split(NUMBER_GROUPS),
min = Math.min(aa.length, bb.length);
for (var i = 0; i < min; i++) {
var x = parseFloat(aa[i]) || aa[i].toLowerCase(),
y = parseFloat(bb[i]) || bb[i].toLowerCase();
if (x < y) return -1;
else if (x > y) return 1;
}
return 0;
};
arr.sort(function(a, b) {
var result;
for (var i = 0; i < columns.length; i++) {
result = naturalSort(a, b, columns[i]);
if (result !== 0) return result; // or -result for decending
}
return 0; //If both are exactly the same
});
console.log(arr);
Bergi's answer is useful and quite interesting, but it changes the API you requested. Here's one that creates the API you were seeking:
var multisort = (function() {
var propLt = R.curry(function(name, a, b) {
return a[name] < b[name];
});
return function(keys, objs) {
if (arguments.length === 0) {throw new TypeError('cannot sort on nothing');}
var fns = R.map(function(key) {
return key.charAt(0) === "-" ?
R.pipe(R.comparator(propLt(R.substringFrom(1, key))), R.multiply(-1)) :
R.comparator(propLt(key));
}, keys);
var sorter = function(a, b) {
return R.reduce(function(acc, fn) {return acc || fn(a, b);}, 0, fns);
}
return arguments.length === 1 ? R.sort(sorter) : R.sort(sorter, objs);
};
}());
multisort(['name', '-number_of_reqs'], arr); //=> sorted clone
It's manually curried rather than calling R.curry because a fair bit of the work is involved in creating the separate sort functions, which could then be reused if you are sorting many lists with the same set of keys. If that's not a concern, this could be simplified a bit.
If you're willing to add another dependency to your project, #panosoft/ramda-utils comes with a compareProps function that does exactly what the original question was asking for.
So, given your original example, to sort descending by budget and then by name, you could do something like this:
var props = ["-budget", "name"];
var comparator = Ru.compareProps(props);
var sortedList = R.sort(comparator, arr);
use the javascript native sort:
Array.prototype.multisort = function(columns) {
var arr = this;
arr.sort(function(a, b) {
return compare(a, b, 0);
});
function compare(a, b, colindex) {
if (colindex >= columns.length) return 0;
var columnname = columns[colindex];
var a_field1 = a[columnname];
var b_field1 = b[columnname];
var asc = (colindex % 2 === 0);
if (a_field1 < b_field1) return asc ? -1 : 1;
else if (a_field1 > b_field1) return asc ? 1 : -1;
else return compare(a, b, colindex + 1);
}
}
var arr = [{ name: 'Foo LLC', budget: 3500, number_of_reqs: 1040 },
{ name: '22nd Amendment',budget: 1500, number_of_reqs: 2000 },
{ name: 'STS 10', budget: 50000,number_of_reqs: 5000 },
{ name: 'STS 10', budget: 50000,number_of_reqs: 500 }];
arr.multisort(['name', 'number_of_reqs']);
if (window.console) window.console.log(arr);
$.each(constructions, function(i,v) {
if ($.inArray(v.name, map[ii].buildings) == -1) {//stuff}
};
Where constructions is an array of objects, each with a unique name. map[ii].buildings is an array containing some of these objects. I want to iterate each object in constructions, checking if its name parameter appears in the objects of map[ii].buildings.
The above code works if the each element in the map[ii].buildings array is just the text string of the object name, but not if the element is the entire object.. close, but no dice >.<
Try using $.grep() instead of $.inArray(); you can specify a function to do the filtering for you.
Instead of checking for -1, you check whether the array that $.grep() returns has length == 0
Simple example: (would be easier if you posted the code / example of what "constructions" objects look like)
var constructions = [{
Name: "Mess hall",
SqFt: 5000
}, {
Name: "Infirmary",
SqFt: 2000
}, {
Name: "Bungalow",
SqFt: 2000
}, {
Name: "HQ",
SqFt: 2000
}];
var buildings = [{
Name: "Infirmary",
SqFt: 2000
}, {
Name: "HQ",
SqFt: 2000
}];
// found buildings will be list of items in "constructions" that is not in "buildings"
var foundBuildings = $.grep(constructions, function (constructionsItem) {
return $.grep(buildings, function (buildingsItem) {
return buildingsItem.Name === constructionsItem.Name
}).length == 0; // == 0 means "not in", and > 0 means "in"
});
// this just renders the results all pretty for ya
$.each(foundBuildings, function (idx, item) {
$("#output").append("<div>" + item.Name + "</div>");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='output'></div>
Example jsFiddle: http://jsfiddle.net/eLeuy9eg/3/
The non-jQuery way of doing this would be to use filter. Something like this:
// pass in an array and the key for which you want values
// it returns an array of those values
function getValues(arr, key) {
return arr.map(function (el) { return el[key]; });
}
function notFoundIn(arr, arr2) {
// grab the names of the buildings
var buildings = getValues(arr2, 'name');
// grab the names from the construction objects and filter
// those that are not in the building array
return getValues(arr, 'name').filter(function (el) {
return buildings.indexOf(el) === -1;
});
}
notFoundIn(constructions, buildings); // eg [ "one", "three" ]
DEMO
You could even add a new method to the array prototype. With this one you can use either simple arrays, or arrays of objects if you pass in a key. Note in this example I've replaced map and filter with loops that perform the same functions, but faster (see comments):
function getValues(arr, key) {
var out = [];
for (var i = 0, l = arr.length; i < l; i++) {
out.push(arr[i][key]);
}
return out;
}
if (!Array.prototype.notFoundIn) {
Array.prototype.notFoundIn = function (inThisArray, key) {
var thisArr = key ? getValues(this, key) : this;
var arrIn = key ? getValues(inThisArray, key) : inThisArray;
var out = [];
for (var i = 0, l = thisArr.length; i < l; i++) {
if (arrIn.indexOf(thisArr[i]) === -1) {
out.push(thisArr[i]);
}
}
return out;
}
}
constructions.notFoundIn(buildings, 'name');
[1, 2, 3].notFoundIn([2]); // [1, 3]
DEMO
I'm wondering if there's a known, built-in/elegant way to find the first element of a JS array matching a given condition. A C# equivalent would be List.Find.
So far I've been using a two-function combo like this:
// Returns the first element of an array that satisfies given predicate
Array.prototype.findFirst = function (predicateCallback) {
if (typeof predicateCallback !== 'function') {
return undefined;
}
for (var i = 0; i < arr.length; i++) {
if (i in this && predicateCallback(this[i])) return this[i];
}
return undefined;
};
// Check if element is not undefined && not null
isNotNullNorUndefined = function (o) {
return (typeof (o) !== 'undefined' && o !== null);
};
And then I can use:
var result = someArray.findFirst(isNotNullNorUndefined);
But since there are so many functional-style array methods in ECMAScript, perhaps there's something out there already like this? I imagine lots of people have to implement stuff like this all the time...
Since ES6 there is the native find method for arrays; this stops enumerating the array once it finds the first match and returns the value.
const result = someArray.find(isNotNullNorUndefined);
Old answer:
I have to post an answer to stop these filter suggestions :-)
since there are so many functional-style array methods in ECMAScript, perhaps there's something out there already like this?
You can use the some Array method to iterate the array until a condition is met (and then stop). Unfortunately it will only return whether the condition was met once, not by which element (or at what index) it was met. So we have to amend it a little:
function find(arr, test, ctx) {
var result = null;
arr.some(function(el, i) {
return test.call(ctx, el, i, arr) ? ((result = el), true) : false;
});
return result;
}
var result = find(someArray, isNotNullNorUndefined);
As of ECMAScript 6, you can use Array.prototype.find for this. This is implemented and working in Firefox (25.0), Chrome (45.0), Edge (12), and Safari (7.1), but not in Internet Explorer or a bunch of other old or uncommon platforms.
For example, x below is 106:
const x = [100,101,102,103,104,105,106,107,108,109].find(function (el) {
return el > 105;
});
console.log(x);
If you want to use this right now but need support for IE or other unsupporting browsers, you can use a shim. I recommend the es6-shim. MDN also offers a shim if for some reason you don't want to put the whole es6-shim into your project. For maximum compatibility you want the es6-shim, because unlike the MDN version it detects buggy native implementations of find and overwrites them (see the comment that begins "Work around bugs in Array#find and Array#findIndex" and the lines immediately following it).
What about using filter and getting the first index from the resulting array?
var result = someArray.filter(isNotNullNorUndefined)[0];
Summary:
For finding the first element in an array which matches a boolean condition we can use the ES6 find()
find() is located on Array.prototype so it can be used on every array.
find() takes a callback where a boolean condition is tested. The function returns the value (not the index!)
Example:
const array = [4, 33, 8, 56, 23];
const found = array.find(element => {
return element > 50;
});
console.log(found); // 56
It should be clear by now that JavaScript offers no such solution natively; here are the closest two derivatives, the most useful first:
Array.prototype.some(fn) offers the desired behaviour of stopping when a condition is met, but returns only whether an element is present; it's not hard to apply some trickery, such as the solution offered by Bergi's answer.
Array.prototype.filter(fn)[0] makes for a great one-liner but is the least efficient, because you throw away N - 1 elements just to get what you need.
Traditional search methods in JavaScript are characterized by returning the index of the found element instead of the element itself or -1. This avoids having to choose a return value from the domain of all possible types; an index can only be a number and negative values are invalid.
Both solutions above don't support offset searching either, so I've decided to write this:
(function(ns) {
ns.search = function(array, callback, offset) {
var size = array.length;
offset = offset || 0;
if (offset >= size || offset <= -size) {
return -1;
} else if (offset < 0) {
offset = size - offset;
}
while (offset < size) {
if (callback(array[offset], offset, array)) {
return offset;
}
++offset;
}
return -1;
};
}(this));
search([1, 2, NaN, 4], Number.isNaN); // 2
search([1, 2, 3, 4], Number.isNaN); // -1
search([1, NaN, 3, NaN], Number.isNaN, 2); // 3
If you're using underscore.js you can use its find and indexOf functions to get exactly what you want:
var index = _.indexOf(your_array, _.find(your_array, function (d) {
return d === true;
}));
Documentation:
http://underscorejs.org/#find
http://underscorejs.org/#indexOf
As of ES 2015, Array.prototype.find() provides for this exact functionality.
For browsers that do not support this feature, the Mozilla Developer Network has provided a polyfill (pasted below):
if (!Array.prototype.find) {
Array.prototype.find = function(predicate) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
};
}
foundElement = myArray[myArray.findIndex(element => //condition here)];
Array.prototype.find() does just that, more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
Use findIndex as other previously written. Here's the full example:
function find(arr, predicate) {
foundIndex = arr.findIndex(predicate);
return foundIndex !== -1 ? arr[foundIndex] : null;
}
And usage is following (we want to find first element in array which has property id === 1).
var firstElement = find(arr, e => e.id === 1);
I have got inspiration from multiple sources on the internet to derive into the solution below. Wanted to take into account both some default value and to provide a way to compare each entry for a generic approach which this solves.
Usage: (giving value "Second")
var defaultItemValue = { id: -1, name: "Undefined" };
var containers: Container[] = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
GetContainer(2).name;
Implementation:
class Container {
id: number;
name: string;
}
public GetContainer(containerId: number): Container {
var comparator = (item: Container): boolean => {
return item.id == containerId;
};
return this.Get<Container>(this.containers, comparator, this.defaultItemValue);
}
private Get<T>(array: T[], comparator: (item: T) => boolean, defaultValue: T): T {
var found: T = null;
array.some(function(element, index) {
if (comparator(element)) {
found = element;
return true;
}
});
if (!found) {
found = defaultValue;
}
return found;
}
const employees = [
{id: 1, name: 'Alice', country: 'Canada'},
{id: 2, name: 'Bob', country: 'Belgium'},
{id: 3, name: 'Carl', country: 'Canada'},
{id: 4, name: 'Dean', country: 'Germany'},
];
// 👇️ filter with 1 condition
const filtered = employees.filter(employee => {
return employee.country === 'Canada';
});
// 👇️ [{id: 1, name: 'Alice', country: 'Canada'},
// {id: 3, name: 'Carl', 'country: 'Canada'}]
console.log(filtered);
// 👇️ filter with 2 conditions
const filtered2 = employees.filter(employee => {
return employee.country === 'Canada' && employee.id === 3;
});
// 👇️ [{id: 3, name: 'Carl', country: 'Canada'}]
console.log('filtered2: ', filtered2);
const employee = employees.find(obj => {
return obj.country === 'Canada';
});
// 👇️ {id: 1, name: 'Alice', country: 'Canada'}
console.log(employee);
There is no built-in function in Javascript to perform this search.
If you are using jQuery you could do a jQuery.inArray(element,array).
A less elegant way that will throw all the right error messages (based on Array.prototype.filter) but will stop iterating on the first result is
function findFirst(arr, test, context) {
var Result = function (v, i) {this.value = v; this.index = i;};
try {
Array.prototype.filter.call(arr, function (v, i, a) {
if (test(v, i, a)) throw new Result(v, i);
}, context);
} catch (e) {
if (e instanceof Result) return e;
throw e;
}
}
Then examples are
findFirst([-2, -1, 0, 1, 2, 3], function (e) {return e > 1 && e % 2;});
// Result {value: 3, index: 5}
findFirst([0, 1, 2, 3], 0); // bad function param
// TypeError: number is not a function
findFirst(0, function () {return true;}); // bad arr param
// undefined
findFirst([1], function (e) {return 0;}); // no match
// undefined
It works by ending filter by using throw.