fastest way to get a subset of a collection in javascript - javascript

I have a master collection of items with unique ID's.
At some point I have a subset of IDs from the master list that belong to some sub grouping if you will. The subset is just a reference of IDs of items that exist in the master list. Is there a way I can ask the master list for just the items that match the IDs in my subset without having to loop through the entire master collection?
Just trying to find the fastest way to do this rather than the standard loop.
//go through master list and determine which items belong to this sub item grouping
for (var item = 0; item < masterListItems.length; ++item ) {
for (var subItem = 0; subItem < subItems.length; ++subItem ) {
if (masterListItems[item].Id == subItems[subItem].Id) { //if it is a sub item
//do some UI specific thing
}
}
}

Here is a solution with jQuery.grep. Filtering in 3 lines :
var master = [{ Id: 3 },{ Id: 1 },{ Id: 2 }]
var ids = [{ Id: 1 },{ Id: 3 }];
$(document).ready(function()
{
// Filtering with 3 lines
idList = [];
$.each(ids,function(index,value) { idList[idList.length] = value.Id; });
elems = $.grep(master,function(element){ return idList.indexOf(element.Id) > -1; });
$.each(elems,function(index,value){
alert(value.Id);
});
});
Edit: Be careful, on Internet Explorer, you will have to define indexOf yourself, as this example :
if(!Array.prototype.indexOf) {
Array.prototype.indexOf = function(needle) {
for(var i = 0; i < this.length; i++) {
if(this[i] === needle) {
return i;
}
}
return -1;
};
}

You can run over the master list once to create "mapping" of the "Id" then one loop over the subset items:
var masterListMapping = new Array();
for (var i = 0; i < masterListItems.length; i++)
masterListMapping[masterListItems[i].Id] = true;
for (var subItem = 0; subItem < subItems.length; subItem++) {
if (masterListMapping[subItems[subItem].Id] == true) { //if it is a sub item
//do some UI specific thing
}
}

//example item is an object, ID is string
var item = { ID: "exampleID112233",
data: 4545 }; //sample item
var masterList = {}; //masterList as a dictionary
//for each item created, use its ID as its key.
masterList["exampleID112233"] = item;
var subCat1 = []; //sublist is an array of ID;
subCat1.push("exampleID112233");
//you can also make new sublists as array, push the item's ID in them.
var subCat2 = ["anotherID334455"];
//iterate through sublist
for (var i = 0; i < subCat1.length; i++) {
//access the referenced item
masterList[subCat1[i]].data += 4;
}
//DELETING: remove the ID from all sublists, then delete it from masterlist.

Why do you want to hardcode referencing when you have language constructs for that?
If you have unique id for items why don't you make them a hash effectively?
// effective {hash}
var masterListItems = {
uid_1: { /* item definition */ },
uid_2: { /* item definition */ },
uid_3: { /* item definition */ },
// ...
};
Then the subset of items can be represented in 3 ways:
// another hash
var subItems = {
uid_6: masterListItems["uid_6"], // effective referencing of the
uid_321: masterListItems["uid_321"], // masterList items
// ...
};
// or array of items
var subItems = [
masterListItems["uid_6"],
masterListItems["uid_321"],
// ...
];
// or array of ids
var subItems = [
"uid_6]",
"uid_321",
// ...
];
The tradeoffs:
hashes are good for uniquely indexed and
effective for lots of get/set operations
arrays are good for numerically indexed data, or when the most common usage is iteration

Related

Browse associative array as circular list

I would like to browse an associative array like a circular list.
First the associative array is defined like this :
array = {item1:array(...), item2:array(...), ...}
When at the first element I browse the array of this element, once arrive at the last element of this array it should passe to the second element and brows it's array, and the same for the last one who must return to the first element.
so I initialize my array as follows:
// Build the associative array
Prot.prototype.additem = function(itemName, itemArray)
{
this.array[itemName] = itemArray; // itemArray is an array
}
// Init the currentItem of the associative array to browse (We don't necessarily start at the first)
Prot.prototype.init = function(itemName)
{
this.currentItem = this.array[itemName];
this.currentItemArray = 0;
}
Prot.prototype.next = function()
{
// here I browse the first array of the first key of my associative array
var index = this.currentItem.indexOf(this.currentItemArray);
index = index +1;
this.currentItemArray = this.currentItem[index];
if (index == (this.currentItemArray.length - 1))
{
// when arrives at the last element of the array of the first key I should pass to the second
return false;
}
else {
return true;
}
}
// I add a set interval at the end so no need for a loop
You'll need an array to know what is the "next" item array. So I would suggest storing the desired order in another array, having just those names.
Here is a possible implementation:
class Prot {
constructor() {
this.itemNames = [];
this.arrays = {};
this.hasData = false;
this.currentIndex = 0;
}
additem(itemName, itemArray) {
if (itemName in this.arrays) throw "duplicate entry";
this.arrays[itemName] = { data: itemArray, index: this.itemNames.length };
this.itemNames.push(itemName); // keep the order
if (itemArray.length) this.hasData = true;
}
init(itemName) {
this.currentItem = this.arrays[itemName];
this.currentIndex = 0;
}
next() {
if (!this.hasData) return;
if (!this.currentItem) this.currentItem = this.arrays[this.itemNames[0]];
var data = this.currentItem.data[this.currentIndex++];
while (this.currentIndex >= this.currentItem.data.length) {
this.currentItem = this.arrays[this.itemNames[(this.currentItem.index+1) % this.itemNames.length]];
this.currentIndex = 0;
}
return data;
}
}
// demo
let obj = new Prot;
// add the arrays:
obj.additem("a", [1, 2, 3]);
obj.additem("b", [4, 5]);
obj.additem("c", [6, 7, 8, 9]);
obj.additem("d", [0]);
// Start at "b":
obj.init("b");
// iterate from there...
for (let i = 0; i < 12; i++) {
console.log(obj.next());
}
There is no such thing as an associative array in JavaScript, but you can use an object instead. A very simple implementation of defining an object and referencing its properties in a circular way would be the following:
// define the object with 6 properties and assiociated values:
var obj={a:123, b:456, c:789, d:666, e:777, f:888};
function getcirc(obj){
// use a "static variable" inside the function:
if(typeof getcirc.i=="undefined") getcirc.i=0;
var keys=Object.keys(obj), k=keys[getcirc.i++%keys.length];
console.log(k,obj[k]);
}
// call the function repeatedly ...
for (var n=0;n<20;n++) getcirc(obj);

Remove items from array using id with Javascript

I have a function like this: pickListSelect array is has all id (numbers) to delete objects in source array, and target array it is to push elements deleted from source array.
function copy(pickListSelect, source, target) {
var i, id;
for (i = 0; i < pickListSelect.length; i++) {
id = pickListSelect[i];
source.splice(id,1);
}
pickListSelect = [];
}
So what I need is delete specific object from source array. I tried with that code but for example if I need to delete object with id=5, it only deleted item 5 from the list.
The structure of source array is this:
[Object, Object, Object, Object, Object, Object, Object, Object, Object]
0:Object
plantId:1
plantName:"Plant 1"
...the rest of others are similar object
You need to find plant in your source by plantId first, and then delete it from original array and push to target. Open console and it should log deleted plants:
var plants = [
{
plantId: 1,
plantName: 'plant 1'
},
{
plantId: 2,
plantName: 'plant 2'
},
{
plantId: 3,
plantName: 'plant 3'
},
{
plantId: 4,
plantName: 'plant 4'
}
];
function copy(pickListSelect, source, target) {
var i, id, el;
for (i = 0; i < pickListSelect.length; i++) {
id = pickListSelect[i];
el = findPlant(source, id);
source.splice(source.indexOf(el), 1);
target.push(el);
}
}
function findPlant (arr, id) {
return arr.filter(function (plant) {
return plant.plantId == id
})[0]
}
var test = [];
copy([2,3], plants, test);
console.log(test);
When you use .splice you need to pass in the start index at which to splice and the amount of items to splice, try this:
source.splice(i,1); // i is your starting index here
array.splice(start, deleteCount[, item1[, item2[, ...]]])
MDN on .splice
Now in your actual code you need to check to see if the id matches and then splice using the above code:
function copy(pickListSelect, source, target) {
var i, id;
for (i = 0; i < pickListSelect.length; i++) {
if (pickListSelect[i].id === someId) {
source.splice(i,1);
}
}
pickListSelect = [];
}
You can take a look at this fiddler here.
I have used underscore.js to find the correct element from source and move it to the target array.
var copy = function(pickListSelect, source, target) {
for (i = 0; i < pickListSelect.length; i++) {
id = pickListSelect[i];
var deleteIndex = _.findIndex(source, {Id: id});
var deletedItem = source.splice(deleteIndex, 1);
target.push(deletedItem[0])
}
pickListSelect = [];
return target;
}
You're not looking up the index of the source array with a matching id. It might be better to do something like this.
var idsToRemove = {};
// build an object of ids to remove (effectively a hashset)
for (var i = 0; i < pickSelectList.length; i++) {
idsToRemove[pickSelectList[i]] = true;
}
// loop through the source array to find any objects with ids to remove
for (var j = 0; j < source.length; j++) {
if (source[j].plantId in idsToRemove) {
target.push(source.splice(j, 1));
}
}

Array of unique JSON values

I'm trying to get an array of unique JSON data based on the comparison of a key value.
In this example, I'm trying to remove any objects with duplicate category values.
Example:
var products = [
{ category: 'fos', name: 'retek' },
{ category: 'fos', name: 'item' },
{ category: 'nyedva', name: 'blabla' },
{ category: 'fos', name: 'gihi' }
];
// array of hold unique values
var uniqueNames = [];
for(i = 0; i< products.length; i++){
if(uniqueNames.indexOf(products[i].category) === -1){
uniqueNames.push(products[i]);
}
}
I'm trying to push to the array any object that doesn't have duplicate category values. Here is a live JSbin.
Please help!
There are several ways to do this, this is one of them: traverse all the items, and filter out the ones which we have already added with that category. For this we use an object to keep which categories we have seen and which ones are new, so we filter only the seen ones:
var seen = {}
var unique = products.filter(function(item){
if(seen.hasOwnProperty(item.category)){
return false;
}else{
seen[item.category] = true;
return true;
}
})
console.log(unique); // only 2 objects
When I am trying to do this, I usually put all of the values into a map as keys, since the map data structure will only allow unique keys. So for this case:
var crops = [ {
id: 0023,
crop: "corn"
},
{
id: 0034,
crop: "corn"
},
{
id: 0222,
crop: "wheat"
}
];
var cropsMap = {};
for(var i = 0; i < crops.length; i++) {
cropsMap[crops[i].crop] = true;
}
var uniqueCrops = Object.keys(cropsMap);
I made a codepen if you want to check it out.
lookup = [];
for (var product, i = 0; product = products[i++];) {
var cat = item.category;
if (!(cat in lookup)) {
lookup[cat] = 1;
result.push(products[cat]);
}
}
Switch
for(i = 0; i< products.length; i++){
if(uniqueNames.indexOf(products[i].category) === -1){
uniqueNames.push(products[i]);
}
}
To
for(i = 0; i< products.length; i++){
if(uniqueNames.indexOf(products[i].category) === -1){
uniqueNames.push(products[i].category); // Push Name of category. Will now not place duplicates into UnqiueNames
}
}
Console
["fos", "nyedva"]

Sort array by order according to another array

I have an object that is being returned from a database like this: [{id:1},{id:2},{id:3}]. I have another array which specified the order the first array should be sorted in, like this: [2,3,1].
I'm looking for a method or algorithm that can take in these two arrays and return [{id:2},{id:3},{id:1}]. Ideally it should be sort of efficient and not n squared.
If you want linear time, first build a hashtable from the first array and then pick items in order by looping the second one:
data = [{id:5},{id:2},{id:9}]
order = [9,5,2]
hash = {}
data.forEach(function(x) { hash[x.id] = x })
sorted = order.map(function(x) { return hash[x] })
document.write(JSON.stringify(sorted))
function sortArrayByOrderArray(arr, orderArray) {
return arr.sort(function(e1, e2) {
return orderArray.indexOf(e1.id) - orderArray.indexOf(e2.id);
});
}
console.log(sortArrayByOrderArray([{id:1},{id:2},{id:3}], [2,3,1]));
In your example, the objects are initially sorted by id, which makes the task pretty easy. But if this is not true in general, you can still sort the objects in linear time according to your array of id values.
The idea is to first make an index that maps each id value to its position, and then to insert each object in the desired position by looking up its id value in the index. This requires iterating over two arrays of length n, resulting in an overall runtime of O(n), or linear time. There is no asymptotically faster runtime because it takes linear time just to read the input array.
function objectsSortedBy(objects, keyName, sortedKeys) {
var n = objects.length,
index = new Array(n);
for (var i = 0; i < n; ++i) { // Get the position of each sorted key.
index[sortedKeys[i]] = i;
}
var sorted = new Array(n);
for (var i = 0; i < n; ++i) { // Look up each object key in the index.
sorted[index[objects[i][keyName]]] = objects[i];
}
return sorted;
}
var objects = [{id: 'Tweety', animal: 'bird'},
{id: 'Mickey', animal: 'mouse'},
{id: 'Sylvester', animal: 'cat'}],
sortedIds = ['Tweety', 'Mickey', 'Sylvester'];
var sortedObjects = objectsSortedBy(objects, 'id', sortedIds);
// Check the result.
for (var i = 0; i < sortedObjects.length; ++i) {
document.write('id: '+sortedObjects[i].id+', animal: '+sortedObjects[i].animal+'<br />');
}
To my understanding, sorting is not necessary; at least in your example, the desired resulting array can be generated in linear time as follows.
var Result;
for ( var i = 0; i < Input.length; i++ )
{
Result[i] = Input[Order[i]-1];
}
Here Result is the desired output, Input is your first array and Order the array containing the desired positions.
var objArray = [{id:1},{id:2},{id:3}];
var sortOrder = [2,3,1];
var newObjArray = [];
for (i in sortOrder) {
newObjArray.push(objArray[(sortOrder[i]) - 1])
};
Why not just create new array and push the value from second array in?? Correct me if i wrong
array1 = [];
array2 = [2,3,1];
for ( var i = 0; i < array2 .length; i++ )
{
array1.push({
id : array2[i]
})
}

How to sort tree of folders

I have a plane tree of folders. This tree has the following properties: id, parent_id, name.
This tree I store in a simple array. The problem is that this array is not sorted.
An element of my array is the simple object like this:
var obj = { id: 1, parent_id: null, name: "Folder" }
I want to sort it in such a way to be able to see some thing like this:
Folder1
Sub_folder1
Sub_sub_folder1
Sub_folder2
Sub_sub_folder2
And so one... I don't want to use recursion and I don't know how to do it properly.
Here is some of my tries. I tryid to add an artificial field which will represented the number of each folder in collection, but it doens't work.
var sort = function(list) {
var f_map = {};
var sorting_index = 1;
var tree = angular.copy(list);
for(var i = 0; i < tree.length; i++) {
var node = tree[i];
f_map[ node.id ]= { index: i, children: [] };
if (node.parent_id) {
f_map[ node.parent_id ].children.push( node.id );
};
var idx = 0;
var visited = {};
for(var key in f_map) {
var index = f_map[key].index;
var node = tree[index];
if (!visited[node.id]) {
node.nuid = idx++;
} else {
visited[node.id] = true;
};
if (f_map[key].children.length) {
var children = f_map[key].children;
for(var i = 0; i < children.length; i++) {
var child_id = children[i];
var child_idx = f_map[child_id].index;
var child = tree[child_idx];
child.nuid = idx++;
visited[child.id] = true;
};
};
};
tree.sort(function(left, right) {
return left.nuid - right.nuid;
});
return tree;
};
Since you're representing the parent pointer as a reference to the id of the parent node, I would first change your representation of the folders into an object representation:
var folders = {
1: {parent_id: null, name: "Folder", path: null},
...
};
I've added a path field, so that I can memoize the results of the following recursive function for finding the full path of a folder:
function path(node) {
if (node.path !== null) return node.path;
if (node.parent_id === null) {
node.path = '/' + node.name;
} else {
node.path = path(folders[node.parent_id]) + '/' + node.name;
}
return node.path;
}
Then we can do a Schwartzian transform by first pulling out the field we want to sort on and a reference to the item:
var keys = [];
Object.keys(folders).map(function (key) {
var folder = folders[key];
keys.push({path: path(folder), id: key});
});
then we can sort the keys array:
keys.sort(function (a, b) {
var apath = a.path;
var bpath = b.path;
// probably the best way to compare folder paths..
return apath.localeCompare(bpath);
});
and finally we can produce the folders in sorted order by traversing the keys array:
var sorted_folders = keys.map(function (item) {
return folders[item.id]; // .name; or maybe .path; ??
});
as is sorted_folders will be a list of folder objects, but per the comment, you can easily pull out the needed properties in this step.
First off, recursion is not slow. It is a nice tool to have in your arsenal. It makes solving certain problems much easier.
Here is an algorithm that should solve it.
1. If the graph can be a forest and not a tree
create a new node root
Make all roots in forest point to this root as parent
2. For every node, create an array (stack) of its children, call it c[i].
3. For each vertex v in tree
c[v.parent].push(v)
4. u = root, i = 0
5. print u
6. while c[root] is not empty and u != root
if c[u] is not empty
u = pop(c[u])
i++
print tab i times
print u
if c[u] is empty and u != root
u = u.parent
i--

Categories