counting object properties with underscore.js - javascript

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.

Related

How to calculate the number of objects I get from the loop Javascript

I filtered my set of users(array) with if(elem.id_verified). I now get 77 users objecta. I just want to take the number of these objects. I tried with console.log(this.numOfunverifiedUsers.length) but i get 77 underfined. My question is how to assemble all objects and get that number. Maybe my logic is going in the wrong direction.
this.users=response.data.users
this.numOfunverifiedUsers = []
this.users.forEach(elem => {
if (elem.id_verified === 0) {
this.numOfunverifiedUsers = elem
console.log(this.numOfunverifiedUsers.length)
}
})
this.numOfunverifiedUsers.push(elem)
Push the element in array.
this.numOfunverifiedUsers = elem , replace it with above
This should work too:
console.log(this.users.filter(function (val) {
return val.id_verified === 0
}).length)
filter items that are id_verified === 0 and count their length.
I think would be better you build that list with a filter:
this.numOfunverifiedUsers = this.users.filter(
user => user.id_verified === 0
);
console.log(this.numOfunverifiedUsers);
console.log(this.numOfunverifiedUsers.length);
If you want to read about filter: Filter method
With this.numOfunverifiedUsers = elem, you are assigning 'elem' to an array reference. As a result, you get exceptions which make the '=' operator return the undefined primitive type (as the result of function errors; see undefined - JavaScript | MDN). What you want to do is either add the element iteratively into the array the "old way", via element assigning, or just use the OOP way via the push method. The former wouldn't require a count function, as you can do something like that:
var count = 0; //outside the forEach
...
if (elem.id_verified === 0) {
{
this.numOfunverifiedUsers[count++]=elem
console.log(count)
}
...
However, as others pointed out, using a filter makes the code much more clean and readable
This would work better with the use of a Filter
console.log(this.users.filter(function (val) {
return val.id_verified === 0
}).length)
filter items that are id_verified === 0 and count their length.

Simple way to force javascript to always return an array

I stumbled upon the YQL API to query for WOEIDs for use in Twitter, but I can see the output is not always in array. The API returns an object and I'm interested in value of response.query.results which returns the following:
if there are no results, it returns null
if there is only one result, it returns an object
if the are multiple results, it returns an array
I want the result to always be an array. I can solve this by checking the result using the following code:
var count = response.query.count;
if(count === 0) {
return [];
} else if(count === 1) {
var arr = [];
arr.push(response.query.results);
return arr;
} else {
return response.query.results;
}
Is there a javascript or lodash function that can simplify the above code? It seems _.forEach and _.toArray will treat each property as an object if provided with a single object.
You could use Array#concat with a default array if response.query.results is falsy.
return [].concat(response.query.results || []);
By having zero as value for response.query.results, you could take the Nullish coalescing operator ?? instead of logical OR ||, which repects all values without undefoned or null
return [].concat(response.query.results ?? []);
https://lodash.com/docs/4.17.4#concat
_.concat([],response.query.results);
would also do it.
but as #Brian pointed out, we need to handle null being equivalent to [] so you can add
_.concat([],_.isNull(response.query.results)?[]:response.query.results);
note that this is more correct because it will work for results with falsey values (like 0 and false etc)
in general, lodash is more robust than built in javascript. this usually works in your favour. one place this can trip you up is if results was a string (which is an array of characters)
https://github.com/lodash/lodash/blob/4.17.4/lodash.js#L6928
function concat() {
var length = arguments.length;
if (!length) {
return [];
}
var args = Array(length - 1),
array = arguments[0],
index = length;
while (index--) {
args[index - 1] = arguments[index];
}
return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
}
Similar to Tom's answer above, the lodash function castArray (https://lodash.com/docs/4.17.15#castArray), introduced in Lodash v4.4, could also work for this. It has the marginal benefit that its intent is slightly more clear that [].concat(x)
const _ = require('lodash')
_.castArray(null) // [null]
_.castArray({a:1}) // [{a:1}]
_.castArray([{a:1},{a:2}] // [{a:1},{a:2}]
To deal with the null, considerations are similar to answers above, depending on how you want to handle unexpected values. A ternary with _.isNull would work, or else ?? is useful, e.g.:
const castArrayRemovingNullUndef = x => _.castArray(x ?? [])
const castArrayRemovingNull = x => _.castArray(_.isNull(x) ? [] :x)
_.castArrayRemovingNull(null) // []
_.castArrayRemovingNull({a:1}) // [{a:1}]
_.castArrayRemovingNull([{a:1},{a:2}] // [{a:1},{a:2}]

What is the right way to use the "in" operator to compile a histogram?

I'm working on implementing a histogram function for arrays in order to return an object that counts how many times an item appears in that array. However whenever I run this code I'm hit by an error message that suggests that the "in" operator cannot be used to search within the object.
var histogram = function(collection) {
collection.reduce(function(combine, item){
if(item in combine){
combine[item]++;
} else{
combine[item] = 1;
}
}, {});
}
var arr = "racecar".split("");
console.log(hist(arr));
I'm guessing the problem here caused by either in or reduce but I can't figure out which it is. Any ideas?
A couple of things: 1) hist isn't the function name, 2) you're not returning anything from the function. I'm not sure how you're getting that error if you're not even calling the function properly, and something the console log would have warned you about.
var histogram = function(collection) {
return collection.reduce(function(combine, item) {
if (item in combine) {
combine[item]++;
} else {
combine[item] = 1;
}
return combine;
}, {});
}
DEMO
Here's a shorter version that doesn't rely on the use of in:
var histogram = function(collection) {
return collection.reduce(function (combine, item) {
combine[item] = (combine[item] || 0) + 1;
return combine;
}, {});
}
DEMO
The problem with in operator is that it searches not only in array indexes, but also in all inherited properties of an Array object.
var ar = [];
'toString' in ar; // prints true
'length' in ar; // prints true
When used in an incorrect context (looking for indexes in an array) it may introduce potential problems that are hard later to debug.
In your case the best is to use Array.prototype.indexOf() or Array.prototype.includes() (from ES6).

Getting boolean result of array

let's say I have an array with n elements of boolean values.
var a = [true,false,true,true,false]
How do I do the OR product of the array.
SO that var result = (true || false || true || true || false) = true
You can use some :
var result = a.some(function(value) {
return value;
});
All these suggestions are far too complex. Just keep it simple. If you want OR then you just need to check if the array contains a single true value:
var result = a.indexOf(true) != -1;
Similarly, if you wanted AND you could just check if it doesn't contain false value, also if you want an empty array to result in false then check the length too:
var result = a.length > 0 && a.indexOf(false) == -1;
Here is a working example, that shows both OR and AND in action.
And here is a performance review of all the current answers, where you can see keeping it simple like this is much quicker than the other suggestions (well, Nina is close to mine as her answer is similar, but less readable IMO). Of course you can argue performance isn't going to be noticed with something like this, but still better to use the fastest method anyway.
Short in one command.
!!~a.indexOf(true)
You may iterate over the array and find it.
var a = [false,false,false,false,false]
var result = a[0];
for(i=0;i<a.length;i++){
result = result || a[i]
}
alert(result);
I hope this would help you
https://jsfiddle.net/0yhhvhu7/3/
From MDN
The Array.prototype.reduce() method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
a.reduce(function(prev, curr) {
return prev || curr;
});

Matching all values, substitute to OR operator

Here is a first sample of code that work as intended: on the rest of the code this is used as a filter and will match 2 items from myids, those 2 where objectId match tWOsQhsP2Z and sStYrIU6lJ:
return myids.objectId === "tWOsQhsP2Z" || myids.objectId === "sStYrIU6lJ";
Because I need to pass arbitrary number of ids from an array, i'm trying to refactor code like so:
return myids.objectId === ("tWOsQhsP2Z" || "sStYrIU6lJ");
Problem with this new code is that filter that use return value will return only one item, the one with objectId that is tWOsQhsP2Z.
Do you know a way how I could refactor this second code so I keep single code "myids.objectId" but return match for ALL objectIds values ?
Sounds like you need something like underscore.js contains() method, would make things a lot simpler all round.
e.g.
return _.contains(arrayOfIds, myids.objectId);
You can use a switch:
switch (myids.objectId) {
case "tWOsQhsP2Z":
case "sStYrIU6lJ":
return true;
}
return false;
If you have an array of values to search, and the list is long and/or you're searching frequently, you can convert the list to an object and then do a property lookup. It's much more efficient that searching through an array.
For a simple constant case, your example would look like:
return myids.objectId in {"tWOsQhsP2Z": 1, "sStYrIU6lJ": 1};
If you start with an array that's server-generated or dynamic:
var knownIds = [ ... ];
then you can convert that to a map:
var idMap = knownIds.reduce(function(m, v) {
m[v] = 1;
return m;
}, {});
Now your lookup would be simply:
return myids.objectId in idMap;

Categories

Resources