I'm playing with Immutable.js. I came across this problem and I wasn't able to find a nice solution: I have two lists, A and B, and I want to filter out some elements from the list A using a custom predicate function and add them to the list B. Both are immutable.
The obvious problem here is that the return value of A.filter(predicate) is a new updated instance and the removed elements are lost. I could first add those filtered elements:
B = B.concat(A.filterNot(predicate));
A = A.filter(predicate);
That would mean cycling over the original list twice. The only way around this is to add a side effect to the filtering function:
let tmp = [];
B = B.filter(el => {
if (!predicate(el)) {
tmp.push(el);
return false;
} else return true;
});
A = A.concat(tmp);
That however looks a bit hacky. I don't think the filter method is supposed to be used this way. Is there a better solution?
Assuming here B is the array you want to filter, and A gets the filtered elements concatenated to it: (like your second code example), I think this is the best you can do.
A.withMutations( (list) => {
B = B.filter(
(el) => {
if (!predicate(el)) {
list.push(el);
return false;
} else return true;
}
);
return list;
});
or, arguably more readable:
A.withMutations( (list) => {
B = B.filter( (el) => { return (!predicate(el)) ? !!list.push(el) : true;
});
return list;
});
If you find yourself moving items from one list to another often, it is probably best to write a method, transferTo that does the above.
From withMutations:
Note: Not all methods can be used on a mutable collection or within
withMutations! Only set, push, pop, shift, unshift and merge may be
used mutatively.
Related
I have an array of ~11,000 JavaScript dictionaries, each representing 1 row in an Excel file.
I want to loop through this array and parse each element into a new datastructure. For example, I might have a function that will count for {"foo": true} or something.
As I have multiple of these functions, my question is would it be better to loop through this array for each function, or have one single loop with functions that parse each element and store it in a global variable?
Ex. I'm currently doing one single loop, and parsing each element into a global variable
const arr = [...]; // array of ~11,000 dictionaries
// example parsing function
let count = 0;
function countFoos(el) {
if (el["foo"] === true) count++;
}
let count2 = 0;
function countBars(el) {
if (el["bar"] === false) count2++;
}
arr.forEach(el => {
countFoos(el);
countBars(el);
});
But would it be better to do it this way?
class Parse {
constructor(arr) {
this.arr = arr;
this.count = 0;
this.count2 = 0;
}
countFoos() {
this.arr.forEach((el) => {
if (el["foo"] === true) this.count++;
});
}
countBars() {
this.arr.forEach((el) => {
if (el["bar"] === false) this.count2++;
});
}
}
const arr = [...]; // array of ~11,000 dictionaries
let x = Parse();
x.countFoos();
x.countBars();
EDIT: I should've clarified early, the examples shown above are just very simplified examples of the production code. Approximately 20 'parsing functions' are being run on for each element, with each of its corresponding global variables being large dictionaries or arrays.
You should generally do just one iteration that calls both functions.
Iterating takes time, so doing two iterations will double the time taken to perform the iterations. How significant this is to the entire application depends on how much work is done in the body of the iteration. If the bodies are very expensive, the iteration time might fall into the noise. But if it's really simple, as in your examples of a simple test and variable increment, the iteration time will probably be significant.
If you are worried about performance, the first method is better as it only involves one iteration over the entire array while the second approach requires two.
If think using classes is more readable, you could simply put write that as one method in the class.
class Parse {
constructor(arr) {
this.arr = arr;
this.count = 0;
this.count2 = 0;
}
count() {
this.arr.forEach((el) => {
countFoos(el), countBars(el);
});
}
countFoos(el){
if(el.foo === true) this.count1++;
}
countBars() {
if(el.bar === false) this.count2++;
}
}
I would approach this by using the Array.prototype.reduce function, which would only require a single pass over the given array. I also would not use a class here as it would not really make sense, but you can if you really want!
function count(arr) {
return arr.reduce(([fooCount, barCount], next) => {
if (next.foo === true) {
fooCount = fooCount + 1
}
if (next.bar === false) {
barCount = barCount + 1
}
return [fooCount, barCount]
}, [0, 0]);
}
const [fooCount, barCount] = count(...);
You can also use generators to accomplish this, which is even better because it doesn't require that you to iterate the entire set of words in the dictionary, but it's a little more unwieldy to use.
This is actually easier to use than other examples that require if statements, because you could quite easily run a battery of functions over each result and add it to the accumulator.
Just remember though that you don't want to optimize before you prove something is a problem. Iterating 22000 objects is obviously more than iterating 11000, but it is still going to be quite fast!
Restricting the number of loops is your best option as it requires less overhead.
Also here is an idea, using the foreach to do the processing with if statements and using a single counter object to hold all of the values so they mean something and can be easily referenced later on.
const arr = [
{"foo" : true,"bar" : false},{"bar" : true,"foo" : false}
];
let counters = {};
function inc(field) {
if (counters[field] == undefined) {
counters[field] = 0;
}
counters[field]++;
}
arr.forEach(el => {
if (el["foo"] === true) {
inc("foo");
}
if (el["bar"] === true) {
inc("bar");
}
});
console.log(counters);
I am trying to compare 2 objets using underscore, specifically I am trying to compare the key/values of "id" (because other things inside will change). I basically want to just check if object A has an item that object B does not have, remove it from object A. Here is my attempt at it :
for(var c=0;c<$scope.types.length;c++){
var real = _.some($scope.storeTempName, function(it) {
return it.id == $scope.types[c].typeId;
});
if(real){
}else{
$scope.types.splice(c,1);
}
}
Where $scope.storeTempName is object B and $scope.types is object A. So if $scope.types has something $scope.storeTempName does not, remove it (tracking by id and typyId for types).
This first attempt I have works, BUT it only will remove the first one. My guess is it's becasue I'm looping from 0 ++ and the index's are changing when i remove the first one so splice is targetting a false item. I am not sure though, and could use some help. Thank you for reading!
Just use _.filter.
$scope.types = _.filter($scope.types, function (type) {
return _.some($scope.storeTempName, function (it) { return it.id == type.typeId })
})
I have a JavaScript array of numbers. My array is defined like this:
var customerIds = [];
I have a function that is responsible for inserting and removing ids to/from this array. Basically, my function looks like this:
function addOrRemove(shouldAdd, customerId) {
if (shouldAdd) {
if (customerIds.contains(customerId) === false) {
customerIds.push(customerId);
}
} else {
customerIds.remove(customerId);
}
}
This function is basically pseudocode. A JavaScript array does not have a contains or remove function. My question is, is there any elegant way of tackling this problem? The best I can come up with is always looping through the array myself and tracking the index of the first item found.
Thank you for any insights you can provide.
The contains can be achieved with Array.prototype.indexOf, like this
if (customerIds.indexOf(customerId) === -1) {
indexOf function returns -1, if it couldn't find the parameter in the array, otherwise the first index of the match. So, if the result is -1, it means that customerIds doesn't contain customerId.
The remove can be achieved with Array.prototype.indexOf and Array.prototype.splice, like this
var index = customerIds.indexOf(customerId);
if (index !== -1) {
customerIds.splice(index, 1);
}
Similarly, indexOf function returns -1, if it couldn't find the parameter in the array, otherwise the first index of the match. So, if the result is -1, we skip deleteing, otherwise splice 1 element starting from the position index.
You can extend the Array method like below after that you are free to use 'contains' and 'remove'
if (!Array.contains)
Array.prototype.contains = function(a) {
for (var i in this) {
if (this[i] == a) return true;
}
return false
}
if (!Array.remove)
Array.prototype.remove = function(a) {
for (var i in this) {
if (this[i] == a) {
this.splice(i, 1);
}
}
}
Use indexOf and splice
function addOrRemove(shouldAdd, customerId) {
if (shouldAdd) {
if (customerIds.indexOf(customerId) == -1) {
customerIds.push(customerId);
}
} else {
var index = customerIds.indexOf(customerId)
customerIds.splice(index, 1);
}
}
You could definitely use the splice and indexOf as stated by #thefourtheye, yet I would like to provide another approach.
Instead of using an array you could use an object.
var customerIds = {};
//This could also be stated as: var customerIds = new Object(); this is just shorthand
function addOrRemove(shouldAdd, customerId)
{
if(shouldAd)
{
if(!customerIds[customerId])
{
customerIds[customerId] = new Object();
customerIds[customerId].enabled = true;
}
}
else
{
if(customerIds[customerId])
{
customerIds[customerId].enabled = false;
}
}
}
You now can query against the customerIds object for a specific customerId
if(customerIds[customerId].enabled)
Using this method not only provides you with the capability of attaching multiple attributes to a given customerId, but also allows you to keep records of all customerIds after disabling (removing).
Unfortunately, in order to truely remove the customerId, you would need to loop through the object and append each property of the object to a new object except for the one you do not want. The function would look like this:
function removeId(customerId)
{
var n_customerIds = new Object();
for(var key in customerIds)
{
if(key != customerId)
{
n_customerIds[key] = customerIds[key];
}
}
customerIds = n_customerIds;
}
In no way am I stating that this would be the proper approach for your implementation, but I am just providing another method of achieving your goal. There are many equivalent ways to solve your dilemma, and it is solely decided by you which method will best suit your projects functionality. I have personally used this method in many projects, as well as I have used the methods posted by others in many other projects. Each method has their pros and cons.
If you do wish to use this method, I would only suggest doing so if you are not collecting many customerIds and do want a lot of customerData per each customerId, or, if you are collecting many customerIds and do not want a lot of customerData per each customerId. If you store a lot of customerData for a lot of customerIds, you will consume a very large amount of memory.
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;
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.