I have some data I'd like to transform using Array.prototype.map. However in the map function there is a chance of an error being thrown by an external function call. I'd like to catch this error and not add that particular object to the returned array. Currently I'm just returning undefined and then using Array.prototype.filter to clear out the undefined values, but this seems like a dirty way to do it.
To clarify, I'm looking for this functionality:
['apple','pear','banana', 'peach'].map(function(fruit){
if (fruit === 'apple') {
return undefined;
}
return 'I love to eat ' + fruit;
});
// ['I love to eat pear', 'I love to eat peach', 'I love to eat banana']
Any existing implementatons of this? Am I just going about this the wrong way?
A more readable way would be;
['apple','pear','banana', 'peach'].filter(function(fruit) {
return fruit === 'apple';
}).map(function(fruit) {
return 'I love eating ' + fruit;
})
With arrow functions & template strings;
['apple','pear','banana', 'peach']
.filter(fruit => fruit === 'apple')
.map(fruit => `I love eating ${fruit}`)
If you don't want to use simple for loop, then instead of map try to use reduce this way:
var result = ['apple','pear','banana', 'peach'].reduce(function(prev, curr){
if (curr === 'apple') {
return prev;
}
prev.push(curr);
return prev;
}, []);
alert(result);
So the idea is that in case of "exception" you simply return prev array without modifying it.
I ended up merging the two methods together into one on the Array prototype. As #Benmj mentioned, you could alternatively put this in a custom utility lib.
Array.prototype.mapDefinedValues = function(handler) {
return this.map(function(item){
return handler(item);
}).filter(function(item){
return item !== undefined;
});
}
Mozilla's MDN for Array.prototype.map() says to use forEach or for-of for that:
Since map builds a new array, using it when you aren't using the returned array is an
anti-pattern; use forEach or for-of instead.
You shouldn't be using map if:
you're not using the array it returns; and/or
you're not returning a value from the callback.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#When_not_to_use_map
As was stated in the comments, you should combine this with a filter. Luckily, this is easy because you can chain array methods:
['apple','pear','banana', 'peach'].map(function(fruit){
if (fruit === 'apple') {
return undefined;
}
return 'I love to eat ' + fruit;
}).filter(function (item) { return item; });
update
One of the tenants of functional programming like this is that you create simple building blocks that do not have side effects. What the OP is describing is essentially adding a side-effect to .map, and this type of behavior should be discouraged.
The answer above can be reduced even further.
This code snippet can actually be reduced even further. Always avoid pushing into array if you can imo.
var result = ['apple','pear','banana', 'peach'].reduce(function(prev, curr){
if (curr === 'apple') {
return prev;
}
return prev.concat(curr);
}, []);
Related
I know that this behaviour is well known and well documented:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
There is no way to stop or break a forEach() loop other than by
throwing an exception. If you need such behavior, the forEach() method
is the wrong tool, use a plain loop instead. If you are testing the
array elements for a predicate and need a Boolean return value, you
can use every() or some() instead. If available, the new methods
find() or findIndex() can be used for early termination upon true
predicates as well.
var theSecond = findTheSecond()
console.log('theSecond is: ' + theSecond)
function findTheSecond(){
[1,2,3].forEach(function(e1) {
console.log('Item:' + e1)
if(e1 === 2) {
return(e1)
}
});
}
My question is why was JavaScript designed like this? Was this an oversight or a deliberate design decision for the language?
These functional iterator methods don't "break" like normal "for" loops probably because when you want to do "forEach" they probably were thinking you intentionally want to do something "for each" value in the array. To do what you want to do there as in "finding" the correct item, you can use "find"
var theSecond = findTheSecond();
console.log('theSecond is: ' + theSecond)
function findTheSecond(){
return (
[1,2,3].find(function(e1) {
console.log('Item: ', e1);
return e1 === 2
})
)
}
Forget the "for loop" which is imperative, get "functional"! There's plenty of methods on the array to choose from i.e. map, reduce, etc.
You could use Array#some with a short cut, if necessary.
var theSecond = findTheSecond();
console.log('theSecond is: ' + theSecond);
function findTheSecond() {
var result;
[1, 2, 3].some(function (el, i) {
console.log('Item:' + el);
if (i === 1) {
result = el;
return true;
}
});
return result;
}
The array is this:
[{name:'name1',id:'id1',val:'001},
...
{name:'name10',id:'id10',val:'010}]
We want to store in the database as a long text string:
name1,id1,001;...;name10,id10,010
values separated by semicolon and comma.
Is there any convenient join or something else to achieve it? (I guess there must be something better than for loop.)
function joinValues(arr, j1, j2) {
return arr.map(function(o) {
return Object.keys(o).map(function(k) {
return o[k];
}).join(j1)
}).join(j2);
}
var obj = [{a:1,b:2},{a:'x',b:'y'}];
joinValues(obj, ',', ';'); // => "1,2;x,y"
array.map(function(o){
var values = Object.keys(o).map(function(key){
return o[key];
});
return values.join(',');
}).reduce(function(a,b){
return a + ';' + b;
});
Beware there might be compat issues depending on your platform (map, reduce, Object.keys not available everywhere yet!)
Also, take into account that properties order in regular objects is not guaranteed. With this approach, theoretically you could end up having name1,id1,001;...;id10,name10,010. That might be an issue when mapping the string back to objects.
I'm afraid there isn't a built-in language feature for that, but if you can use ES6, it can look quite elegant
Object.values = obj => Object.keys(obj).map(key => obj[key]);
// so to convert your data
data.map(o => Object.values(o).join(',')).join(';');
Does Underscore.js have a findLast() method or equivalent?
What is the best way to do .find() but return the last item that matches in Collection?
Reverse the list and then use find:
_.find(list.slice().reverse(), iterator);
Read MDN for the documentation on reverse.
Unfortunately a collection in underscore may be either an array or an object. If your collection is an array then you're in luck. You can use reverse. However if it's an object then you'll need to do this instead:
_.find(Object.keys(list).reverse(), function (key) {
return iterator(list[key], key, list);
});
You could write a findLast function for yourself:
_.mixin({
findLast: function (list, iterator, context) {
if (list instanceof Array)
return _.find(list.slice().reverse(), iterator, context);
else return _.find(Object.keys(list).reverse(), function (key) {
return iterator.call(context, list[key], key, list);
});
}
});
Now you can use findLast like any other underscore method.
Underscore 1.8.0 introduced a method findLastIndex which can be used to accomplish this.
var numbers = [1, 2, 3, 4];
var index = _.findLastIndex(numbers, isOddNumber);
if (index > 0) { console.log(numbers[index]); }
// returns 3
Using reverse, as suggested by #AaditMShah, is the easiest solution, but be aware that it manipulates the array in place. If you need to preserve the order of elements, you'd have to call reverse a second time, after you are done.
If you don't want to use reverse, you can
use Lodash instead, which provides _.findLast
grab the relevant code from Lodash, spread out over findLast and forEachRight and make your own findLast.
This is what it looks like if you only deal with arrays and don't care about objects:
function findLast (array, callback, thisArg) {
var index = array.length,
last;
callback = callback && typeof thisArg == 'undefined' ? callback : _.bind(callback, thisArg);
while (index--) {
if (callback(array[index], index, array) == true) {
last = array[index];
break;
}
}
return last;
}
(It works, but I haven't tested it properly. So to anyone reading this, please run a few tests first and don't just copy the code.)
So I'm trying to think of a better way to do this with underscore:
state.attributes = _.reduce(list, function(memo, item){
memo['neighborhood'] = (memo['neighborhood'] || []);
var isNew = true;
_.each(memo['neighborhood'], function(hood){
if (hood.name === item.data.neighborhood) {
hood.count++; isNew=false;
}
});
if(isNew){
memo['neighborhood'].push({name:item.data.neighborhood, count:1});
}
return memo;
});
I would like to combine the various names of the list into a list of unique names with a count of how many times each unique name occurs. It seems like exactly the kind of problem underscore was designed to solve, yet the best solution I could think of seems less than elegant.
I'm not an underscore.js user, but I guess _.groupBy() suits this scenario:
var attributes = _.groupBy(list, function (item) {
return item.data.neighborhood
})
It doesn't returns an array in the exact way you want, but it contains all the information you need. So you have in attributes["foo"] all the items that have "foo" as neighborhood value property, and therefore in attributes["foo"].length the count of them.
Maybe there is a better underscore.js way, but you can already apply other optimizations:
Instead of using an array to keep track of the name and the count, use a name: count map. This can be easily done with an object:
state.attributes = _.reduce(list, function(memo, item){
var n = item.data.neighborhood;
memo['neighborhood'] = (memo['neighborhood'] || {});
memo['neighborhood'][n] = memo['neighborhood'][n] + 1 || 1;
return memo;
});
This works, because if item.data.neighborhood is not in the list yet, memo['neighborhood'][item.data.neighborhood] will return undefined and undefined + 1 returns NaN.
Since NaN evaluates to false, the expression NaN || 1 will result in 1.
I'm doing very frequent searches in arrays of objects and have been using jQuery.inArray(). However, I'm having speed and memory issues and one of the most called methods according to my profiler is jQuery.inArray(). What's the word on the street about its performance? Should I switch to a simple for loop?
My specific function is:
function findPoint(point, list)
{
var l = list.map(function anonMapToId(p) { return p.id });
var found = jQuery.inArray(point.id, l);
return found;
}
Is perhaps list.map() is more to blame?
Well internally inArray makes a simple loop, I would recommend you to check if there is a native Array.prototype.indexOf implementation and use it instead of inArray if available:
function findPoint(point, list) {
var l = list.map(function anonMapToId(p) { return p.id });
var found = ('indexOf' in Array.prototype) ? l.indexOf(point.id)
: jQuery.inArray(point.id, l);
return found;
}
The Array.prototype.indexOf method has been introduced in browsers that implement JavaScript 1.6, and it will be part of the ECMAScript 5 standard.
Native implementations are way faster than non native ones.
What you really want is a Array.prototype.filter.
function findPoint(point, list)
{
return list.filter(function anonFilterToId(p) {
return p.id === point.id;
}).length > 0;
}
Even is the inArray function were slow, you're still creating a full new array for every search. I suppose it would be better to redesign this search, by e.g. creating the id-list before finding the points, and using that one to search into:
I'm doing a join of the array to turn it into a string and avoid the loop section like this :
var strList = ","+array.join(",")+",";
return strList.indexOf(","+search+",") !== -1 ? true : false;
if the array is huge, it can hurt, but for a small list it's much faster than the loop solution
PS I'm adding an ending coma to avoid look a like
I always use lastIndexOf when I want to know if there's a string in my array.
So, its something like this:
var str = 'a';
var arr = ['a','b','c'];
if( arr.lastIndexOf(str) > -1){
alert("String " + str + " was found in array set");
} else {
alert("String " + str + " was not found");
}
If you just want to find a string in array, I do believe this might be the best practice.