Group javascript items by one property - javascript

My question is related to this question. You will have to first read it.
var ids = "1*2*3";
var Name ="John*Brain*Andy";
var Code ="A12*B22*B22";
Now that I have an array of javascript objects. I want to group my objects based on CODE. So there can be duplicate codes in that code string.
As per the above changed strings, I have same code for Brain and Andy. So, now I want two arrays. In one there will be only one object containing details of only John and in the other object there will be two objects containing details of Brain and Andy.
Just for example I've taken 3 items. In actual there can be many and also there can be many set of distinct codes.
UPDATE
I needed the structure like the one built in groupMap object by the #Pointy. But I will use #patrick's code to achieve that structure. Many thanks to both of them.

It is a little hard to tell the exact resulting structure that you want.
This code:
// Split values into arrays
Code = Code.split('*');
Name = Name.split('*');
ids = ids.split('*');
// cache the length of one and create the result object
var length = Code.length;
var result = {};
// Iterate over each array item
// If we come across a new code,
// add it to result with an empty array
for(var i = 0; i < length; i++) {
if(Code[i] in result == false) {
result[ Code[i] ] = [];
}
// Push a new object into the Code at "i" with the Name and ID at "i"
result[ Code[i] ].push({ name:Name[i], id:ids[i] });
}
Will produce this structure:
// Resulting object
{
// A12 has array with one object
A12: [ {id: "1", name: "John"} ],
// B22 has array with two objects
B22: [ {id: "2", name: "Brain"},
{id: "3", name: "Andy"}
]
}

Split the strings on "*" so that you have 3 arrays.
Build objects from like-indexed elements of each array.
While building those objects, collect a second object that contains arrays for each "Code" value.
Code:
function toGroups(ids, names, codes) {
ids = ids.split('*');
names = names.split('*');
codes = codes.split('*');
if (ids.length !== names.length || ids.length !== codes.length)
throw "Invalid strings";
var objects = [], groupMap = {};
for (var i = 0; i < ids.length; ++i) {
var o = { id: ids[i], name: names[i], code: code[i] };
objects.push(o);
if (groupMap[o.code]) {
groupMap[o.code].push(o);
else
groupMap[o.code] = [o];
}
return { objects: objects, groupMap: groupMap };
}
The "two arrays" you say you want will be in the "groupMap" property of the object returned by that function.

Related

How to Insert Key Value Pairs inside Nested Arrays in JavaScript

I need to achieve the same output but as you see the length of the ID's array is zero because I cannot achieve this output using push command, it generates errors like:
push is not a function
Cannot use indexOf for undefined or false
I need to solve this array with a push command and make the output exactly like below but I cannot use the each function because the length is zero.
var RepeaterClass = {
Repeaters: [],
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
},
};
Implementing of this code:
ID_1: Array(0)
category: Array(1)
0: "dog"
animals: Array(2)
0: "dog"
1: "cat"
As others have commented it's not entirely clear what you're asking here. This code sort of works if you fix the line var Repeaters =[];
I think the confusion is arising because we create Repeaters as an array, but then I think you must be calling collectRepeaterValues with strings for rep_id and cat_id (e.g. 'ID_1' and 'animals') to get the output you are showing. It should be called with numbers if you want to create arrays. You can't access an array element with a string.
If you call with strings JavaScript is going to create object properties on the array when you do Repeaters[rep_id] = Repeaters[rep_id] || []. That is to say, if we execute the statement Repeaters['ID_1'] = [] in JavaScript it's not doing array assignment even if Repeaters is an array. It will create an object property called ID_1 and makes its value the empty array.
The snippets below show calling the (corrected) object with numbers and with strings and the results.
As an aside, the if statement in collectRepeaterValues is not working.
Now we're back on what the question really is. Do you want arrays, which have to be indexed by numbers of course, or do you want objects with string properties?
// CALLING WITH STRINGS
var RepeaterClass = {
Repeaters: [], // Fixed so it's an object property
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
}
}
// What I think you're doing?
RepeaterClass.collectRepeaterValues("ID_1", "category", "dog");
RepeaterClass.collectRepeaterValues("ID_1", "animals", "dog");
RepeaterClass.collectRepeaterValues("ID_1", "animals", "cat");
// At the top level RepeaterClass.Repeaters is an empty array with a 'ID_1' property
// Array length is zero...
console.log(RepeaterClass.Repeaters.length); // 0
// But we have a property ID_1, that is itself an array of zero length with category
// and animals properties that are arrays
console.log(RepeaterClass.Repeaters.ID_1.category[0]); // dog
console.log(RepeaterClass.Repeaters.ID_1.animals[0]); // dog
console.log(RepeaterClass.Repeaters.ID_1.animals[1]); // cat
// Note that this IS the result at the end of the question
// EDIT: You can iterate over the properties with for..in
console.log('Iterating categories on ID_1:');
for (var cat_id in RepeaterClass.Repeaters.ID_1) {
console.log(cat_id);
}
// CALLING WITH NUMBERS
var RepeaterClass = {
Repeaters: [], // Fixed so it's an object property
collectRepeaterValues: function (rep_id, cat_id, element_id) {
this.Repeaters[rep_id] = this.Repeaters[rep_id] || [];
this.Repeaters[rep_id][cat_id] = this.Repeaters[rep_id][cat_id] || [];
if (-1 === this.Repeaters[rep_id][cat_id].indexOf(element_id)) {
this.Repeaters[rep_id][cat_id].push(element_id);
}
}
}
// How this code is meant to be called I think
RepeaterClass.collectRepeaterValues(0, 0, "dog");
RepeaterClass.collectRepeaterValues(0, 1, "dog");
RepeaterClass.collectRepeaterValues(0, 1, "cat");
// At the top level RepeaterClass.Repeaters is now an array structure
console.log(RepeaterClass.Repeaters.length); // 1
console.log(RepeaterClass.Repeaters[0][0][0]); // dog
console.log(RepeaterClass.Repeaters[0][1][0]); // dog
console.log(RepeaterClass.Repeaters[0][1][1]); // cat
The Only Solution I found is by create another array to store the elements inside the repeater then push it in the main Repeaters array outside the funtion
But Still cannot achieve it in the same function.
var RepeaterClass = {
Repeaters: {},
validated: {},
collectRepeaterValues: function( cat_id, element_id ) {
// this.Repeaters[ rep_id ] = this.Repeaters[ rep_id ] || [];
this.validated[ cat_id ] = this.validated[ cat_id ] || [];
if ( -1 === this.validated[ cat_id ].indexOf( element_id ) ) {
this.validated[ cat_id ].push( element_id );
}
}
AnotherFunction: function() {
_.each( REP, function( repDetails, repID ) {
_.each( repDetails, function( value ) {
/* 1. Call the Collector */
collectRepeaterValues( value['cat'], value['id'] );
} );
/* 2. push the validated output inside the main array */
this.Repeaters[ repID ] = this.Repeaters[ repID ] || [];
this.Repeaters[ repID ] = this.validated;
/* Empty for another session */
this.validated = {};
} );
}
}

Javascript add elements to an object from another object

I have one object. The first element inside the data object looks like this:
data[0] = {name:"Bob", model:"Tesla", color:"white"};
and a second object, whose first element looks like this:
new_data[0] = {salary:"50000", age:"34"};
data and new_data are the same length, and each element inside of the new_data object needs to be appended onto the correlating data object, to make something like this:
data[0] = {name:"Bob", model:"Tesla", color:"white", salary:"50000", age:"34"};
I've used concat before to add elements into a single line object ( var
people = ["Dan","Bob"];
people.concat("Mike");
, but that same idea doesn't work here:
for ( var i = 0;i<data.length; i++ ) {
data[i] = data[i].concat(new_data[i]);
}
How do I go about looping through this?
As such you have tagged your question with jQuery, I've used its $.extend() method below (jQuery Documentation).
This is just a one liner solution for your case. By passing true to this method, you can easily merge object2 into object1, recursively. jQuery is smart to figure out that both of your objects are array of same length, so the output is an array and each item in the resulting array is a merged result from both objects.
Object.assign is quite new (ES6) and may not be supported in all browsers (Source). But this jQuery way can be a useful time saver for supporting all the browsers.
var collection1 = [{
name: "Bob",
model: "Tesla",
color: "white"
},
{
name: "Bob 1",
model: "Tesla 1",
color: "white 1"
}
];
var collection2 = [{
salary: "50000",
age: "34"
},
{
salary: "50001",
age: "35"
}
];
var result = $.extend(true, collection1, collection2);
console.log(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
MDN
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
How do I go about looping through this?
With Object.assign() in mind we can loop through it like below:
var data = [];
data[0] = {name:"Bob", model:"Tesla", color:"white"};
data[1] = {name:"Martin", model:"Ford", color:"Blue"};
data[2] = {name:"Danny", model:"BMW", color:"Purple"};
var new_data =[];
new_data[0] = {salary:"50000", age:"34"};
new_data[1] = {salary:"45000", age:"24"};
new_data[2] = {salary:"10000", age:"39"};
for(var i = 0; i < data.length; i++){
data[i] = Object.assign(data[i], new_data[i]);
console.log(data[i]);
}
You can use jQuery.extend function like shown below. It extends existing data[i] object with properties from new_data[i] object.
for ( var i = 0; i<data.length; i++ ) {
jQuery.extend(data[i], new_data[i]);
}
The most efficient way is to loop through every element in new_data and apply it to data.
for(var key in new_data[0]){
data[0][key] = new_data[0][key];
}
What you need to use if Object.assign like so :
for ( var i = 0;i < data.length; i++ ) {
Object.assign(data[i], new_data[i]);
}
This will alter the content of data.
concat is meant to be used with arrays. It will append one (or more) array(s) at the end of another.
let people = ["Dan","Bob"];
people.concat(["Mike"]); // people is now ["Dan", "Bob", "Mike"]
You could utilize the keys of one of the object to merge the two. This would work in IE9 and up as well. If you need to guard against overriding a property in your base object then you could add a quick truthy check for that key before assigning in the forEach invocation.
var obj1 = {id:1, name: "bob"}
var obj2 = {dob: "2000101"};
Object
.keys(obj1)
.forEach(function(k){
obj2[k] = obj1[k];
});
console.log(obj2);

When to use object and when array

I have an situation where I have 3 different arrays with very different amounts of objects in it. I've read many questions and blog posts about this but Im still unsure when to use what.
PS! My biggest problem is that I need to iterate and push (perfect for arrays), also find if exists in array and delete (more suitable for objects). Specific order is not required.
I can't allow having same object in both array1 and array1clicked
because they should perform different actions.
When it's best to use object and when array in my example? What should I replace with object and what should stay as array? Im pretty sure that amounts of objects in it also matters, right?
My current code:
//Objects in arrays are literally custom {objects} with custom prototypes and html
var array1 = [ 20 objects ];
var array1clicked = [];
var array2 = [ 250 objects ];
var array2clicked = [];
var array3 = [ 50 000 objects ];
var array3clicked = [];
//Each object in arrays has event attached
objecthtml.click(function() {
//Add to clicked array
array1clicked.push(thisobject);
//Remove from initial array
var index = array1.indexOf(thisobject);
if (index > -1) {
array1.splice(index, 1);
}
}
//Same with array2 and array3 objects
//Iterations on different conditions
var array1count = array1.length;
var array1clickedcount = array1clicked.length;
//Same with array2 and array3
if(condition1) {
for(a = 0; a < array1count; a++) {
array1[a].div.style.visibility = 'hidden';
}
//Same with array2 and array3 objects
for(a = 0; a < array1clickedcount; a++) {
array1clicked[a].div.style.visibility = 'visible';
}
//Same with array2clicked and array3clicked objects
}
else if(condition2) {
for(a = 0; a < array1count; a++) {
array1[a].div.style.visibility = 'visible';
}
//Same with array2 and array3 objects
for(a = 0; a < array1clickedcount; a++) {
array1clicked[a].div.style.visibility = 'hidden';
}
//Same with array2clicked and array3clicked objects
}
It seems you want a data structure with these operations:
Iteration
Insert
Delete
Search
With arrays, the problem is that searches and deletions (with reindexing) are slow.
With objects, the problem is that the property names can only be strings.
The perfect structure is a set.
var s = new Set();
s.add(123); // insert
s.has(123); // search
s.delete(123); // delete
s.values(); // iterator
In your case, I think you have to use just Array.
In common case, you could use object to keep references and push some values into it, but If you wanna iterate on this, I think you have to use Array.

Find duplicate object values in an array and merge them - JAVASCRIPT

I have an array of objects which contain certain duplicate properties: Following is the array sample:
var jsonData = [{x:12, machine1: 7}, {x:15, machine2:7},{x:12, machine2: 8}];
So what i need is to merge the objects with same values of x like the following array:
var jsonData = [{x:12, machine1:7, machine2:8}, {x:15, machine2:7}]
I like the lodash library.
https://lodash.com/docs#groupBy
_.groupBy(jsonData, 'x') produces:
12: [ {x=12, machine1=7}, {x=12, machine2=8} ],
15: [ {x=15, machine2=7} ]
your desired result is achieved like this:
var jsonData = [{x:12, machine1: 7}, {x:15, machine2:7},{x:12, machine2: 8}];
var groupedByX = _.groupBy(jsonData, 'x');
var result = [];
_.forEach(groupedByX, function(value, key){
var obj = {};
for(var i=0; i<value.length; i++) {
_.defaults(obj, value[i]);
}
result.push(obj);
});
I'm not sure if you're looking for pure JavaScript, but if you are, here's one solution. It's a bit heavy on nesting, but it gets the job done.
// Loop through all objects in the array
for (var i = 0; i < jsonData.length; i++) {
// Loop through all of the objects beyond i
// Don't increment automatically; we will do this later
for (var j = i+1; j < jsonData.length; ) {
// Check if our x values are a match
if (jsonData[i].x == jsonData[j].x) {
// Loop through all of the keys in our matching object
for (var key in jsonData[j]) {
// Ensure the key actually belongs to the object
// This is to avoid any prototype inheritance problems
if (jsonData[j].hasOwnProperty(key)) {
// Copy over the values to the first object
// Note this will overwrite any values if the key already exists!
jsonData[i][key] = jsonData[j][key];
}
}
// After copying the matching object, delete it from the array
// By deleting this object, the "next" object in the array moves back one
// Therefore it will be what j is prior to being incremented
// This is why we don't automatically increment
jsonData.splice(j, 1);
} else {
// If there's no match, increment to the next object to check
j++;
}
}
}
Note there is no defensive code in this sample; you probably want to add a few checks to make sure the data you have is formatted correctly before passing it along.
Also keep in mind that you might have to decide how to handle instances where two keys overlap but do not match (e.g. two objects both having machine1, but one with the value of 5 and the other with the value of 9). As is, whatever object comes later in the array will take precedence.
const mergeUnique = (list, $M = new Map(), id) => {
list.map(e => $M.has(e[id]) ? $M.set(e[id], { ...e, ...$M.get(e[id]) }) : $M.set(e[id], e));
return Array.from($M.values());
};
id would be x in your case
i created a jsperf with email as identifier: https://jsperf.com/mergeobjectswithmap/
it's a lot faster :)

map items of array to another array

I have an array:
arr = [ 1, 2 , 3 ]
And another array where i hold DOM elements as
Elem = [ 1, 3]
I need to iterate over arr and only do stuff if the index match. For example since I have elem 1 and 3 when I loop through arr something should only happen for 1 and 3 and 2 should be skipped since there is no elem 2.
Someone told me to look into associative arrays and I wonder how I can do this with the least number of lines.
I want the code to be simple and readable and so far all the examples of associative arrays make no sense and are bloated.
for(var i = 0;i<arr.length;i++){
if(Elem.indexOf(arr[i])>-1){
//Elem contains arr[i] (contains object that at index i in arr)
//will be called only for 1 and 3 in arr
arr[i] = ... //do what you want with this object.
}
}
Do you mean this?
I modified the second array a bit to allow defining multiple actions in one place. I am not sure if I understand you correctly.
// array of DOM objects available
var arr = ['object1-selector', 'object2-selector', 'object3-selector'];
// array of actions with items that the method should be applied to
var actions = [
{
items: ['object1-selector', 'object3-selector'],
perform: function(elem) {
alert(elem);
}
},
{
items: ['object2-selector'],
perform: function(elem) {
alert(elem);
}
},
{
items: ['object4-selector'],
perform: function(elem) {
alert(elem);
}
}
];
//forEach loop that iterates over actions and checks if selector exists.
//If yes - it invokes the method
actions.forEach(function(action) {
action.items.forEach(function(item) {
if(arr.indexOf(item) > -1) {
action.perform(item);
}
});
});
If you want to have actions defined in one place and objects in a multidimensional array - let me know. I will try to adjust the example. If you don't store selectors but whole DOM objects, just modify the items: array and loop, that checks if element exists.
Oh, and here is jsfiddle: http://jsfiddle.net/3WJxc/2/. jQuery used only for alert() to show you working example.
Not really sure how you identify the elements in the second array but this is my suggestion. Array with ids
var arr = [ "id_1", "id_2", "id_3" ]
var Elem = {
"id_1": html_element,
"id_2": html_element,
"id_3": html_element
}
Then all you need to do is
for( var i = 0; i < arr.length; i++ ) {
if( Elem[ arr[i] ] ) {
// do stuff
}
}

Categories