Javascript - how to detect difference from two arrays - javascript

Sorry for the maybe misleading title.
I have two arrays, one contains the defaults and another one contains products.
What I am trying to do is compare the two so that you can add/remove as many products as you like, but you can't have less products as the default.
Lets say
default = [1,2]
products = [1,1,1,1,2,2,2,3,4,5,6,7,8,9]
this should work.
But you can't have something like this:
default = [1,2]
products = [2,2,2,3,4,5,6,7,8,9]
because at least the same amount of products in the default array is required, and in the last example, 1 is not included in the products array.
I am using this to compare the two arrays:
Array.prototype.containsArray = function ( array /*, index, last*/ ) {
if( arguments[1] ) {
var index = arguments[1], last = arguments[2];
} else {
var index = 0, last = 0; this.sort(); array.sort();
};
return index == array.length
|| ( last = this.indexOf( array[index], last ) ) > -1
&& this.containsArray( array, ++index, ++last );
};
arr1.containsArray( arr2 )
which works. In my function (the one used to add/remove products) I tried to have the check like this:
removeDeviceToZone = function(zone, ID) {
if (products.containsArray(default) {
return products = removeFromArray(products, ID);
}
};
but the problem is that at the time the check is executed, the array is still correct, but it won't be anymore as soon as a product is removed. What's the best way to have the check prevent what the array will be after removing the item without really removing it yet? Is it even possible? is it the best approach to do this? thanks

You should use every function which accepts a callback provided method applied on every item in the array.
The every() method tests whether all elements in the array pass the
test implemented by the provided function.
function containsArray(defaultArray, products){
return defaultArray.every(function(item){
return products.indexOf(item)!=-1;
});
}
let defaultArray = [1,2]
let products = [1,1,1,1,2,2,2,3,4,5,6,7,8,9];
let products2=[2,2,2,3,4,5,6,7,8,9];
let contains=containsArray(defaultArray,products);
let contains2=containsArray(defaultArray,products2);
console.log(products.toString()+'->'+contains);
console.log(products2.toString()+'->'+contains2);
When you delete items you should check if the containsArray keeps to be true. In the other words you have to check if the containsArray function returns true after remove element.If yes, return products. Otherwise, return the old products array.
removeDeviceToZone = function(zone, ID) {
let productsCopy=products;
let products=removeFromArray(products, ID);
if (containsArray(default,products) && containsArray(default,productsCopy) {
return products;
}
else
return productsCopy;
};

Simply putting,
Clone the original array in another variable (clonedArray) and compare the two after you are done deleting.
:)

Simplify the logic:
delete whatever you're going to delete
add the default values back in after each delete operation, e.g.:
for each default, check if it's already in the array and push if not
optionally sort the result if order matters to you
That's a simple, idempotent operation.
Alternatively, create a class that has the defaults as a property and the user-selected items as a separate property, and which merges both together conditionally as needed whenever necessary, e.g. basket.getMergedBasket().
Alternatively, instead of trying to maintain two lists, make those products objects if they aren't already which have an appropriate flag, e.g.:
products = [{ id: 1, mandatory: true }, ...]
That would be a really simple object oriented approach.

Related

JS reduceRight is causing arrays to become objects, how can I fix the logic?

I have an json object:
json = {
"array": [80, 50]
}
A third party jsoneditor, returns a path variable as a list of strings or index to the value. I use event listeners to detect changes to the value and I store those changes in a separate object to later submit to a form.
For example, if I change 50 to 60, this is what is returned:
node.path = ["array", 1]
node.value = 60
I am using reduceRight() to normally map the changes to the object to the changed object, however when the value is an Array it is converting the array to an object.
let delta = node.path.reduceRight((obj, elem) => ({[elem]: obj}), node.value)
//returns
delta = {array: {1: 60}}
//instead of
delta = {array: [80, 60]}
How can I check the original json and if the field is an Array object, don't reduce this into an object but keep the original Array and update the Array with the changed value within the Array? I need to submit the entire array into the form.
EDIT:
I know that I can do some custom logic within the reduce to check the original json and get the unchanged part of the array. I just dont know how to achieve that
let delta = node.path.reduceRight( function (obj, elem) {
//some logic here to account for arrays and get original array list and change only slected index
else {
return {[elem]: obj}), node.value
}
});
EDIT:
Alternatively, how can can I get the nested keys from the node.path and find the original array and then just update the array index? The path can sometimes be nested sothe path is always going to be the Array-like structure.
//this does not work
let orig_key_val = json[node.path]
Thanks!
This seems to work, finally... I used a regular reduce() and check if the key is an Array if it is store my original Array into a tmp object and save that key. The next time the reduce() comes around, if the index is on the last path element then set the tmp[tmp_key][key] to my target value and return the adjusted for Array object instead.
I can see how this will not work for nested json objects but for now I dont have any... Unless someone can show me how to figure that one out this is my implementation for now.
let tmp = {};
let tmp_key;
let delta = node.path.reduce((val, key, index, array) => {
if (Array.isArray(json[key])) {
tmp[key] = json[key]
tmp_key = key;
} else if (Object.keys(tmp).length !== 0 && index === node.path.length-1) {
tmp[tmp_key][key] = node.value;
return tmp
} else
return {[key]: val}
}, node.value);

Javascript array filtering and mapping

I keep asking the same question in terms of react and not getting a clear answer, so ive decided to attempt to extrapolate conceptually what i want to do and see if i cant get some answers on how i can proceed in react.
filters= ["Bill Johnson", "hasStartDocs"]
mappedArray =
[
{
name:'Larry',
docs:{
startDocs:[{...}]
},
workers:{
sales:["Bill Johnson"]
}
]
So if filter[i] has a space{' '} check all arrays under workers for strings like filter[i] and then map
filteredMappedArray based on that
if filter[i] does not have a space create a new string slicing the first 3 chars of the string making the first letter of that new string lower case (thatString = "startDocs") eval(resoStatus.${thatString}.length > 0) then map filteredMappedArray like that.
so after that for every instance of [i] you would have a unique map. so if someone clicked 5 filters there would be a filteredMappedArray for each which i guess you would .concat() and .reduce() if they have the same _id.
I dont need someone to help with the string manipulation. I need a nudge in the right direction on how to utilize both filters [] and mappedArray [] to create 1 filteredMappedArray. please and thank you.
for(i=0,i<filters.length,i++){
filters.map(filter => filter.includes(' ') //map mappedArray based on rules above)
filters.map(filter => filter.includes(/^has/ //map mappedArray based on rules above)
}
this gives you
[filteredMappedArray1]
[filteredMappedArray2]
bigArray = filteredMappedArray1.concat(filteredMappedArray2)
smallArray = bigArray.forEach(map //if the map is unique delete it if the map isnt unique keep it but remove all the duplicates)
As best as I can make out, you aren't doing any mapping, just filtering the mappedArray to limit it to entries that match at least one filter in filters.
If so, here's how to do that (see comments) provided you don't have lots more filters than you have entries in mappedArray (if you did, you'd structure it differently, but that seems unlikely to me):
// The `length` check prevents filtering if there are no filters; that's usually how people
// implement filters, but remove it if that's not right in your case
const filteredArray = filters.length === 0 ? mappedArray : mappedArray.filter(entry => {
// `some` returns `true` if the callback returns a truthy value (and
// stops loopihng); it returns `false` if it reaches the end of the
// array without the callback rturning a truthy value. So basically
// this is saying "return true if any filter matches."
return filters.some(filter => {
if (filter.includes(" ")) {
// Check `workers` arrays
for (const array of Object.values(entry.workers)) {
if (array.includes(filter)) {
return true;
}
}
} else {
// Check `docs` for at least one entry for the given type
const key = /*...strip leading three, change case of fourth...*/;
const array = entry.docs.key];
if (array && array.length > 0) { // There's at least one entry
return true;
}
}
return false;
});
});

How do I find objects with a property inside another object in JavaScript

I have a object with all my users, like so:var users = {user1:{}, user2:{}}, And every user has a isPlaying property. How do I get all users that have isPlaying false?
You should use Object.keys, Array.prototype.filter and Array.prototype.map:
// This will turn users object properties into a string array
// of user names
var userNames = Object.keys(users);
// #1 You need to filter which users aren't playing. So, you
// filter accessing users object by user name and you check that
// user.isPlaying is false
//
// #2 Using Array.prototype.map, you turn user names into user objects
// by projecting each user name into the user object!
var usersNotPlaying = userNames.filter(function(userName) {
return !users[userName].isPlaying;
}).map(function(userName) {
return users[userName];
});
If it would be done using ECMA-Script 6, you could do using arrow functions:
// Compact and nicer!
var usersNotPlaying = Object.keys(users)
.filter(userName => users[userName].isPlaying)
.map(userName => users[userName]);
Using Array.prototype.reduce
As #RobG has pointed out, you can also use Array.prototype.reduce.
While I don't want to overlap his new and own answer, I believe that reduce approach is more practical if it returns an array of user objects not playing.
Basically, if you return an object instead of an array, the issue is that another caller (i.e. a function which calls the one doing the so-called reduce) may need to call reduce again to perform a new operation, while an array is already prepared to fluently call other Array.prototype functions like map, filter, forEach...
The code would look this way:
// #1 We turn user properties into an array of property names
// #2 Then we call "reduce" on the user property name array. Reduce
// takes a callback that will be called for every array item and it receives
// the array reference given as second parameter of "reduce" after
// the callback.
// #3 If the user is not playing, we add the user object to the resulting array
// #4 Finally, "reduce" returns the array that was passed as second argument
// and contains user objects not playing ;)
var usersNotPlaying = Object.keys(users).reduce(function (result, userName) {
if (!users[userName].isPlaying)
result.push(users[userName]);
return result;
}, []); // <-- [] is the new array which will accumulate each user not playing
Clearly using Array.prototype.reduce concentrates both map and filter in a single loop and, in large array, reducing should outperform "filter+map" approach, because looping a large array twice once to filter users not playing and looping again to map them into objects again can be heavy...
Summary: I would still use filter+map over reduce when we talk about few items because sometimes readability/productivity is more important than optimization, and in our case, it seems like filter+map approach requires less explanations (self-documented code!) than reduce.
Anyway, readability/productivity is subjective to who does the actual coding...
Iterate through your users object:
var list = [];
for (var key in users) {
if (users[key].isPlaying === false) {
list.push(key);
}
}
This will give you a list of all users who have an isPlaying property that is false.
If you would like all of the user objects where isPlaying is false, you can add the objects themselves instead:
var list = [];
for (var key in users) {
if (users[key].isPlaying === false) {
list.push(users[key]);
}
}
This can also be achieved using Array.prototype.reduce, which is a great all round tool. It starts with getting an array of the names:
var userNames = Object.keys(users);
To return an array just the user names where isPlaying is false, you can do:
var usersNotPlaying = userNames.reduce(function(names, name) {
if (!users[name].isPlaying) {
names.push(name);
}
return names}, []);
To return an object of user objects with their names as keys is similar:
var usersNotPlaying = userNames.reduce(function(names, name) {
if (!users[name].isPlaying) {
names[name] = users[name];
}
return names}, {});
You could also use forEach in a similar way, however since it returns undefined the object or array collecting the members must be initialised in an outer scope first:
var usersNotPlaying = {};
userNames.forEach(function(name) {
if (!users[name].isPlaying) {
usersNotPlaying[name] = users[name];
}
});
You can also use for..in:
var usersNotPlaying = {};
for (var user in users) {
if (users.hasOwnProperty(user) && !users[user].isPlaying) {
usersNotPlaying[user] = users[user];
}
}
All of the above can return an array of names, array of user objects or object of user objects. Choose whatever suits. ;-)
Please try the JS code below: set all the isPlaying to false.
var json_object={};//{"user1":{"isPlaying":"false"},"user2":{"isPlaying":"ture"}};
json_object['user1']={"isPlaying":"false"};
json_object['user2']={"isPlaying":"ture"};
console.log(json_object);
for(var key in json_object){
if(json_object[key].isPlaying === "false"){/*do what you want*/}
}
console.log(json_object);

Merge array into a single array

If this were .NET, I'd ask how to convert List<List<MyClass> to List<MyClass>. However, I'm not very good with javascript and don't know how to ask that as a question using Javascript terminology!
My javascript object comes through like
And is created as:
js_datasets.push({
"DataItem0": {
lzabel: "This",
data: [[1408710276000, null],[1408710276000, 15]]
},
"DataItem1": {
lzabel: "That",
data: [[1408710276000, null],[1408710276000, 15]]
},
});
js_datasets.push({
"DataItem22": {
lzabel: "And other",
data: [[1408710276000, null],[1408710276000, 5]]
},
"DataItem23": {
lzabel: "And lastly",
data: [[1408710276000, null],[1408710276000, 1]]
},
});
Each object is the same "type" (if it matters).
I'd like to create a single list but I am failing to do so. My efforts are
var myDataSet = []; //this is the results of what I want, ideally as a single list
for (var i = 0; i < js_datasets.length; i++) {
if (i==0) {
myDataSet.push(js_datasets[i]);
}
else {
myDataSet.concat(js_datasets[i]);//does nothing
myDataSet.join(js_datasets[i]);//does nothing
}
...more logic
As you can see with the above, I've tried using push, concat and join.
If I update the code to only use push (and never use concat and join) then I get all the values I want, but again, as an array within an array.
Using concat and join do not add to the list.
So, if we can assume the 12 items in the array (pictured) all contain 10 items, I'd like to have a single list of the 120 items!
How can I simply convert this multidimension array (is it multidimension) to a single dimension array.
This will be a bit complicated, as the items in your Array js_datasets are not Arrays, but a more generic Object. This means you can't assume the keys will be in order if you try to read them
Lets write some helper functions to account for this;
function dataItemCollectionToArray(o) {
var keys = Object.keys(o);
// assuming no non-DataItem keys, so next line commented out
// keys = keys.filter(function (e) {return e.indexOf("DataItem") === 0;});
keys.sort(function (a, b) { // ensure you will get the desired order
return +a.slice(8) - +b.slice(8);
});
return keys.map(function (e) {return o[e];});
}
Now you can loop over js_datasets performing this conversion
var myDataSet = [], i;
for (i = 0; i < js_datasets.length; ++i) {
// assuming no gaps, if you need to add gaps, also find min, max indices
// in `dataItemCollectionToArray`, and check them in each iteration here
myDataSet.push.apply(myDataSet, dataItemCollectionToArray(js_datasets[i]));
}
Please note that Object.keys and Array.prototype.map may require polifills if you wish to support old browsers, i.e. IE<=8
An easier solution however, may be to re-write how js_datasets is constructed so that the Objects you are pushing are more Array-like or indeed pushing true Arrays, perhaps with a couple extra properties so you know the offset for the first index. This would mean you can use flatten methods that you'll find around the internet

Sort array by key OR: Why is my for loop executing out of order?

I have an array of objects which I need placed in a certain order, depending on some configuration data. I am having a problem with itterating through the array in the proper order. I thought that if I made the array, and then stepped through with a for loop, I would be able to execute the code correctly. It is working great except in one use case, in which I add the fourth item to the array and then go back to the third.
links[0] = foo
links[1] = bar
links[2] = foobar
links[3] = a_herring
links[4] = a_shrubery
order = [] //loaded from JSON, works has the numbers 1 2 3 or 4 as values
//in this case:
order[0] = 1
order[1] = 2
order[2] = 4
order[3] = false
order[4] = 3
for(x in order){
if(order[x]){
printOrder[order[x]]=links[x]
//remember that in this case order[0] would
}
This should give me an array that looks like this:
//var printOrder[undefined,foo,bar,a_shrubbery,foobar]
But when I try to itterate through the array:
for(x in printOrder){
printOrder[x].link.appendChild(printOrder[x].image)
printOrder[x].appendChild(printOrder[x].link)
printOrder[x].appendChild(printOrder[x].text)
document.getElementById("myDiv").appendChild(printOrder[x]);
}
I get foo, bar, foobar, a_shrubbery as the output instead.
I need to either sort this array by key value, or step through it in the correct order.
Iterating over the numerically-index properties of Array instances should always be done with a numeric index:
for (var x = 0; x < printOrder.length; ++x) {
// whatever with printOrder[x]
}
Using the "for ... in" form of the statement won't get you predictable ordering, as you've seen, and it can have other weird effects too (particularly when you mix in JavaScript frameworks or tool libraries or whatever). It's used for iterating through the property names of an object, and it doesn't treat Array instances specially.
You need to create a function for finding values in an array like this:
Array.prototype.indexOf = function(value)
{
var i = this.length;
while ( i-- )
{
if ( this[ i ] == value ) return i;
}
return -1;
};
You can then use it like this:
//NOTICE: We're looping through LINKS not ORDER
for ( var i = 0; i < links.length; i++ )
{
var index = order.indexOf( i );
//It's in the order array
if ( index != -1 ) printOrder[ i ] = links[ i ];
}
REMEMBER: You need to make sure the values returned in json are integers. If they're strings, then you'll need to convert the integers to string when passed to indexOf.
The function you have in your question works as you suggest it should.
http://jsfiddle.net/NRP2D/8/ .
Clearly in this simplified case you have removed whatever error you are making in the real case.

Categories