An efficient way to get the difference between two arrays of objects? - javascript

I have two arrays of objects:
var a = [ {'id': 20}, {'id': 15}, {'id': 10}, {'id': 17}, {'id': 23} ];
var b = [ {'id': 90}, {'id': 15}, {'id': 17}, {'id': 23} ];
I'd like to get objects which are in a, but not in b. Results from this example would be:
{'id': 20} and {'id': 10}.
Because the arrays could be large, I need an efficient way to do this.

// Make hashtable of ids in B
var bIds = {}
b.forEach(function(obj){
bIds[obj.id] = obj;
});
// Return all elements in A, unless in B
return a.filter(function(obj){
return !(obj.id in bIds);
});
very minor addendum: If the lists are very large and you wish to avoid the factor of 2 extra memory, you could store the objects in a hashmap in the first place instead of using lists, assuming the ids are unique: a = {20:{etc:...}, 15:{etc:...}, 10:{etc:...}, 17:{etc:...}, 23:{etc:...}}. I'd personally do this. Alternatively: Secondly, javascript sorts lists in-place so it doesn't use more memory. e.g. a.sort((x,y)=>x.id-y.id) Sorting would be worse than the above because it's O(N log(N)). But if you had to sort it anyway, there is an O(N) algorithm that involves two sorted lists: namely, you consider both lists together, and repeatedly take the leftmost (smallest) element from the lists (that is examine, then increment a pointer/bookmark from the list you took). This is just like merge sort but with a little bit more care to find identical items... and maybe pesky to code. Thirdly, if the lists are legacy code and you want to convert it to a hashmap without memory overhead, you can also do so element-by-element by repeatedly popping the elements off of the lists and into hashmaps.

With lodash 4.12.0 you can use _.differenceBy.
_.differenceBy(a, b, 'id');

A general way to do this would be:
put all objects from b into a hashtable
iterate over a, for each item check if it is in the hashtable
A lot of programming environments have set and/or HashSet implementations these days, which make it very simple to do this.
In special cases, other ways might be more efficient. If, for example, your elements were byte-sized values, and a and b both fairly big, then I would use a boolean array "flags" with 256 elements, initialize all to false. Then, for each element x of b, set flags[x] to true. Then iterate over a, and for each y in a, check if flags[y] is set.

If you not adverse to including a library use underscore.js it has a good intersection function
http://documentcloud.github.com/underscore/

Related

Intersect multiple arrays of objects

So first of all, I am not expecting a specific solution to my problem, but instead some insights from more experienced developers that could enlighten me and put me on the right track. As I am not yet experienced enough in algorithms and data structures and I take this as a challenge for myself.
I have n number of arrays, where n >= 2.
They all contain objects and in the end, I want an array that contains only the common elements between all these arrays.
array1 = [{ id: 1 }, { id: 2 }, { id: 6 }, { id: 10 }]
array2 = [{ id: 2 }, { id: 4 }, { id: 10 }]
array3 = [{ id: 2 }, { id: 3 }, { id: 10 }]
arrayOfArrays = [array1, array2, array3]
intersect = [{ id: 2 }, { id: 10 }]
How would one approach this problem? I have read solutions using Divide And Conquer, or Hash tables, and even using the lodash library but I would like to implement my own solution for once and not rely on anything external, and at the same time practice algorithms.
For efficiency, I would start by locating the shortest array. This should be the one you work with. You can run a reduce on the arrayOfArrays to iterate through and return the index of the shortest length.
const shortestIndex = arrayOfArrays.reduce((accumulator, currentArray, currentIndex) => currentArray.length < arrayOfArrays[index] ? currentIndex : accumulator, 0);
Take the shortest array and call the reduce function again, this will iterate through the array and allow you to accumulate a final value. The second parameter is the starting value, which is a new array.
shortestArray.reduce((accumulator, currentObject) => /*TODO*/, [])
For the code, we basically need to loop through the remaining arrays and make sure it exists in all of them. You can use the every function since it will fail fast meaning the first array it doesn't exist in will trigger it to return false.
Inside the every you can call some to check if there is at least one match.
isMatch = remainingArrays.every(array => array.some(object => object.id === currentObject.id))
If it's a match, add it to the accumulator which will be your final result. Otherwise, just return the accumulator.
return isMatch ? [...accumulator, currentObject] : accumulator;
Putting all that together should get you a decent solution. I'm sure there are more optimizations that could be made, but that's where I would start.
reduce
every
some
The general solution is to iterate through an input and check for each value whether it exists in all of the other inputs. (Time complexity: O(l * n * l) where n is number of arrays and l is the average length of an array)
Following the ideas of the other two answers, we can improve this brute-force approach a bit by
iterating through the smallest input
using a Set for efficient lookup of ids instead of iteration
so it becomes (with O(l * n + min_l * n) = O(n * l))
const arrayOfIdSets = arrayOfArrays.map(arr =>
new Set(arr.map(val => val.id))
);
const smallestArray = arrayOfArrays.reduce((smallest, arr) =>
smallest.length < arr.length ? smallest : arr
);
const intersection = smallestArray.filter(val =>
arrayOfIdSets.every(set => set.has(val.id))
);
A good way to approach these kinds of problems, both in interviews and in just regular life, is to think of the most obvious approach you can come up with, no matter how inefficient, and think think about how you can improve it. This is usually called a "brute force" approach.
So for this problem, perhaps an obvious but inefficient approach would be to iterate through every item in array1 and check if it is in both array2 and array 3, and note it down (in another array) if it is. Then repeat again for each item in array2 and in array 3, making sure to only note down items you haven't noted down before.
We can see that will be inefficient because we'll be looking for a single item in an array many times, which is quite slow for an array. But it'll work!
Now we can get to work improving our solution. One thing to notice is that finding the intersection of 3 arrays is the same as finding the intersection of the third array with the intersection of the first and second array. So we can look for a solution to the simpler problem of the intersection of 2 arrays, to build one of an intersection for 3 arrays.
This is where it's handy to know your datastructures. You want to be able to ask the question, "does this structure contain a particular element?" as quickly as possible. Think about what structures are good for that kind of a lookup (known as search). More experienced engineers have this memorized/learned, but you can reference something like https://www.bigocheatsheet.com/ to see that sets are good at this.
I'll stop there to not give the full solution, but once you've seen that sets are fast at both insertion and search, think about how you can use that to solve your problem.

Few large arrays, or one large array of few variables?

apologies if this question has been asked before but I'm finding it hard to word the question in a way that might have been asked before.
Q: Is it more efficient to have something like:
mass[128] = {0.0}
speed[128] = {0.0}
age[128] = {0}
Or:
properties[128] = {mass=0.0, speed=0.0, age=0}
And why? Is there a simple rule to always bear in mind, (are few larger arrays better than many small etc)?
I'm writing in JS using Chrome. Reading and writing to elements very often.
Thanks very much!
In general, the answer here is: Do what makes the most sense to let you write the simplest, clearest code; and worry about any performance or memory issue if you actually run into one.
Using an array of objects with named properties will likely be more efficient in terms of access time on a modern JavaScript engine, and will likely be less efficient in terms of memory use. In both cases, the difference will be incredibly minor and probably imperceptible.
If your values are numbers and your arrays can be of fixed size, you might use typed arrays, since they really are arrays (where as normal arrays aren't1 unless the JavaScript engine can do it as an optimization). But there are downsides to typed arrays (their being fixed size, for instance), so again, if and when it becomes necessary...
Example of an array of objects with named properties:
var properties = [
{mass: 0, speed: 0, age: 0},
{mass: 1, speed: 1, age: 1},
// ...
];
If you're using ES2015 (you said you're using Chrome, so you can), you might make that a const:
const properties = [
{mass: 0, speed: 0, age: 0},
{mass: 1, speed: 1, age: 1},
// ...
];
That only makes properties a constant, not the contents of the array it points to, so you can still add, remove, or amend entries as desired.
1 That's a post on my anemic little blog.

Accessing associative array inside an object

I have looked everywhere for this but nobody seems to use associative arrays in objects. Here is my object:
var player = {
Level: 1,
Stats: [{Defense : 5}, {Attack: 1}, {Luck: 3}]
};
I need to access the values of Defense, Attack, and Luck, but how?
I have tried this but it hasn't worked:
player.Stats.Defense
player.Stats.Attack
player.Stats.Luck
Any ideas? Thanks!
P.S. Does it make a difference that I am using jQuery?
You've said you're in control of the structure. If so, change it to this:
var player = {
Level: 1,
Stats: {Defense : 5, Attack: 1, Luck: 3}
};
Note that Stats is now an object, not an array. Then you access that information the way you tried to, player.Stats.Defense and so on. There's no reason to make Stats an array of dissimilar objects, that just makes your life difficult.
You've used the term "associative array" which makes me think you have a PHP background. That term isn't commonly used in the JavaScript world, to avoid confusion with arrays. "Object," "map," or "dictionary" are the terms usually used, probably in that order, all referring to objects ({}). Probably nine times out of ten, if you would use an associative array for something in PHP, you'd use an object for it in JavaScript (in addition to using objects for the same sort of thing you use objects for in PHP).
P.S. Does it make a difference that I am using jQuery?
No, this is language-level rather than library-level, but it's a perfectly reasonable question.
(Making this a CW answer because it's basically what all the comments on the question are saying.)
as Stats: [{Defense : 5}, {Attack: 1}, {Luck: 3}] is array of objects, you need to do:
player.Stats[0].Defense
player.Stats[1].Attack
player.Stats[2].Luck
Here player.Stats is an array of objects. So you'll have to use index for accessing those objects.
var player = {
Level: 1,
Stats: [{Defense : 5}, {Attack: 1}, {Luck: 3}]
};
Use these :
player.Stats[0].Defense
player.Stats[1].Attack
player.Stats[2].Luck

Is it possible to chain array.push() in Javascript?

I have 3 separate arrays and I'm looking to load them all into to a single array. Am I able to use .push() several arrays into one? Is something like this possible?
var activeMembers=[]; // Active Users
var noactiveMsg=[]; // Non-Active Users with a Pending Message
var noactiveNomsg=[]; // Non-Active Users without a Pending Message
var chatCenterMembers=[]; // Final Array of Chat Center Members
chatCenterMembers.push(activeMembers).push(noactiveMsg).push(noactiveNomsg);
Is there a way to chain .push()?
You're looking for the (vanilla) JavaScript method Array.concat().
Returns a new array comprised of this array joined with other array(s) and/or value(s).
Example, following your code:
chatCenterMembers = chatCenterMembers
.concat(activeMembers)
.concat(noactiveMsg)
.concat(noactiveNomsg);
chatCenterMembers.push(activeMembers,noactiveMsg,noactiveNomsg)
This question is quite confusing. First of all, the question seems to be asking for a way to combine multiple arrays into one single array containing the elements of all the arrays. However, the accepted answer provides a solution for creating an array of arrays. Since the text in the question suggests merging the elements of multiple arrays into one array while the code example uses push with arrays as arguments, it's quite ambigious what the OP wants.
Furthermore, several answers have suggested using concat. While that fulfills the requirement of returning the resulting array after adding the provided element, and is fine for small sets of data and/or where performance and memory is not an issue, it's inefficient if dealing with large arrays, since each concat operation will allocate a new array, copy all the elements of the old array into it, then copy all the elements of the provided array into it, and dereference the old array (as opposed to simply adding elements to the same array object).
Consider calling concat N times, adding C elements each time:
allocate new array, copy C elements
allocate new array, copy 2 * C elements
allocate new array, copy 3 * C elements
...
A different approach would be to create your own method, either as a separate function or adding it to the Array prototype:
Array.prototype.append = function(e) {
this.push(e);
return this;
}
With this, you could do
[1, 2, 3].append(4).append(5).append(6)
without allocating more than one array object in total.
It could perhaps also be mentioned that with ES2015, the spread operator can be used to add all the elements of an array to another array using push:
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2); // arr1 is now [1, 2, 3, 4, 5, 6]
This will however not fulfill the requirement of returning the resulting array for chaining, but the append method above could be used to merge multiple arrays like this:
chatCenterMembers = activeMembers.append(...noactiveMsg).append(...noactiveNomsg);
You can do it instead with .concat().
var chatCenterMembers=[];
chatCenterMembers = chatCenterMembers.concat(activeMembers, noactiveMsg, noactiveNomsg);
Since on one else has posted it:
var chatCenterMembers = activeMembers.concat(noactiveMsg, noactiveNomsg);
push AND unshift chaining
I actually came here looking for both but didn't see any good answer so far for unshift so I'll note that here as well.
push chaining is straight forward
const list = ['hi', 'there']
.concat(['buddy'])
// list is now ['hi', 'there', 'buddy']
but unshift chaining is weird
// need to use concat + map to do unshift chaining
const list = ['hi', 'there']
.concat(['buddy'])
.map((e, i, a) => i == 0 ? a[a.length - 1] : a[i-1])
// list is now ['buddy', 'hi', 'there']
As you can see using map there is a 3rd param given for the array you are using so this gives you power to do all sorts of odd things.

JavaScript switch case: anyway to force the order of the elements as they are written out?

I've got results being returned to a Google Mapping application in the div sidebar. The results are names of businesses that belong to categories that the client wants returned in a certain order. And unfortunately it's not a straight alpha sort. So one category begins with F and the second one with a C and the last three are A's, for example.
So I need my switch, which works, but naturally insists on dropping the values in alpha sorted (as they are returned from the DB that way) order as that's how it encounters them. What's the best way to set this up so I can grab my preferred categories with their associated company names in the arbitrary order the client has asked for?
Thanks!
Can you iterate over the categories in the order you want them in, and find the object to which it is associated?
E.g. (pseudocode)
var categories = [ 'F', 'C', 'A1', 'A2', 'A3' ].map(function (category) {
return businesses.filter(function (business) {
return business.category === category;
});
});
So the missing step in the answer given here was HOW the map would be implemented and HOW the JS snippet could be implemented. Anyway, I ended up having to ask that as a separate question and finally got a nice working example for an answer.
Russ wrote:
The code given looks most likely to be
using the jQuery JavaScript library
that has some useful functions such as
map() for manipulating arrays.
If we go back to the original problem,
you need to order a list of categories
based on the client's preference.
Let's create a an object literal to
map the ordering
var map = {
F : 5,
C : 3,
A1 : 1,
A2 : 4,
A3 : 2
}
We can use this map to order the array
using the sort method
var array = ['F', 'C', 'A1', 'A2', 'A3'];
array.sort(function(a,b) {
return map[a] - map[b];
});
This returns us ["A1", "A3", "C", "A2", "F"]
Anyway, I wanted to make sure this was included on this thread for anyone searching for this issue in the future or anyone following along right now. Thanks for everyone's input!

Categories

Resources