Grouping elements of array on the basis of property - javascript

I have a array as follows:
data = [
{
"id":1
"name":"london"
},
{
"id":2
"name":"paris"
},
{
"id":3
"name":"london"
},
{
"id":4
"name":"paris"
},
{
"id":5
"name":"australia"
},
{
"id":6
"name":"newzearland"
}
]
At runtime this array can have n number of elements. I want to group this array with respect to name attribute. All the elements with same name should be moved to a separate array. I don't know the what value can name have in advance. This is coming at runtime. For example, from above array I want final output as follows:
output:
newArray1 = [
{
"id":1
"name":"london"
},
{
"id":3
"name":"london"
}
]
newArray2 = [
{
"id":2
"name":"paris"
},
{
"id":4
"name":"paris"
}
]
newArray3 = [
{
"id":5
"name":"australia"
}
]
newArray4 = [
{
"id":6
"name":"newzearland"
}
]
How can I do that?

As Teemu has already pointed out in a comment, creating new variables to store the data is not ideal. You would have no way of knowing how many groups you've created and using variables that you can't be sure exist is not the best way to write code. Fortunately, JavaScript has objects, which can store data like this in a much cleaner way. Here's the code I've come up with:
function groupBy(arr, key) {
let res = {}
for (let element of arr) {
if (res.hasOwnProperty(element[key])) {
res[element[key]].push(element)
} else {
res[element[key]] = [element]
}
}
return res
}
This code is not the best, most efficient code ever, but it is written to be easier to understand for someone still learning. This code loops over every element in your data and checks whether our result already contains an array for elements with that name. If there's already an array for elements with that name, the current element is added to it. If there isn't one, a new one is created with the current element inside it. To do exactly what you want, you'd call this function with groupBy(data, "name") and assign it to a new variable like groupedData (THIS DOES NOT MODIFY THE DATA, IT RETURNS A NEW OBJECT OF GROUPED DATA) .

Start by getting all the unique .names, then map them to the original array filtered by each .name:
const data = [{
"id": 1, "name": "london"
},
{
"id": 2, "name": "paris"
},
{
"id": 3, "name": "london"
},
{
"id": 4, "name": "paris"
},
{
"id": 5, "name": "australia"
},
{
"id": 6, "name": "newzearland"
}
];
const newData = [...new Set(data
//Get all names in an array
.map(({name}) => name))]
//For each name filter original array by name
.map(n => data.filter(({name}) => n === name));
console.log( newData );
//OUTPUT: [newArray1, newArray2, .....]

You can get the expected result with grouping by key approach.
const data = [{"id":1,"name":"london"},{"id":2,"name":"paris"},{"id":3,"name":"london"},{"id":4,"name":"paris"},{"id":5,"name":"australia"},{"id":6,"name":"newzearland"}];
const result = Object.values(data.reduce((acc, obj) =>
({ ...acc, [obj.name]: [...(acc[obj.name] ?? []), obj] }), {}));
console.log(result);
const [newArray1, newArray2, newArray3, newArray4, ...rest] = result;
console.log('newArray1:', newArray1);
console.log('newArray2:', newArray2);
console.log('newArray3:', newArray3);
console.log('newArray4:', newArray4);
.as-console-wrapper{min-height: 100%!important; top: 0}

Related

Sort array of objects by given incomplete array of orders in Javascript

I have an array of objects:
var items = [
{
"id":"sugar",
"type": 'eatables'
},
{
"id":"petrol",
"type": 'utility'
},
{
"id":"apple",
"type": 'fruits'
},
{
"id":"mango",
"type": 'fruits'
},
{
"id":"book",
"type": 'education'
}
];
Now I have another array of orders with the help of which I want to sort items array:
var orders = [
{
"id":"sugar",
"order":5
},
{
"id":"book",
"order":1
}
];
Now what I am trying so far in my logic is that I am putting so many loops that it is totally creating mess.
Can anyone suggest me with a short and optimized logic for this?
One approach could be creating one dictionary which will keep the order for every element. Also, I've iterated the whole items array to store the position for the elements that are not in the orders array.
First of all, I'll declare one array which keep the whole orders, thus one array with 1..N elements.
var orderNumbers = Array.from({length: items.length}, (_, v) => v + 1);
Then I started to create the dictionary by iterating orders array and remove orders from orderNumbers.
The last step is iterating the items array and use shift method to "pop" the first element.
The final dictionary will look like
{
"sugar": 2,
"book": 3,
"petrol": 1,
"apple": 4,
"mango": 5
}
In this code I used one dictionary because its lookup has complexity of O(1).
var items = [ { "id":"sugar", "type": 'eatables' }, { "id":"petrol", "type": 'utility' }, { "id":"apple", "type": 'fruits' }, { "id":"mango", "type": 'fruits' }, { "id":"book", "type": 'education' } ], orders = [ { "id":"sugar", "order":2 }, { "id":"book", "order":3 } ], orderNumbers = Array.from({length: items.length}, (_, v) => v + 1);
var ordersDict = orders.reduce((acc, item) => {
acc[item.id] = item.order;
//remove from order numbers
let index = orderNumbers.findIndex(el => el == item.order);
orderNumbers.splice(index, 1);
return acc;
}, {});
for(let i = 0; i < items.length; i++){
if(!ordersDict.hasOwnProperty(items[i].id)){
ordersDict[items[i].id] = orderNumbers[0];
orderNumbers.shift();
}
}
//sort the array
items.sort((a,b) => ordersDict[a.id] - ordersDict[b.id]);
console.log(items);
let oorder = new Object();
orders.map(item=>{oorder[item.id]=item.order});
var new_items = [];
items.map(item=>{new_items[oorder[item.id]-1]=item});

how to update a particular json data object which belongs to browser local-storage

This is Browser localstorage Object referred as dataset
let dataset = localStorage.getItem('dataset') !== null ? leech : [];
[
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
This is the initial data object available I want to add more field to a particular id.
This is what I want :
[
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd",
"status":1
}
]
This my code to find the particular id
const user = dataset.find(user => user.id == 456);
Now how can I add status to user and update the user in the dataset?
You've already found the user by using Array.prototype.find() so all you need to do then is add the status property
// const dataset = JSON.parse(localStorage.getItem("dataset"))
const dataset = [{"id":123,"name":"abc"},{"id":456,"name":"bcd"}]
const user = dataset.find(({ id }) => id === 456)
if (user) {
user.status = 1
}
console.info(dataset)
.as-console-wrapper { max-height: 100% !important }
If you then want to store the modified data back into localStorage, use localStorage.setItem() and JSON.stringify()
localStorage.setItem("dataset", JSON.stringify(dataset))
If you want keep dataset initial value, and would like to get a new array, you can use Array.reduce() method.
const dataset = [
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
const output = dataset.reduce((acc, cur) => {
if (cur.id === 456) cur.status = 1;
acc.push(cur);
return acc;
}, []);
console.log(output);
If you want to update dataset, you can use Array.forEach() method.
const dataset = [
{
"id": 123,
"name": "abc"
},
{
"id": 456,
"name": "bcd"
}
]
dataset.forEach(user => {
if (user.id === 456) user.status = 1;
});
console.log(dataset);
You could do with Array#Findindex with callback return function. so could pass the originaldata,searchId and update object. In this method you could updated object easily
Why i suggest findIndex
Because findindex not running entire loop or iteration. If the match
detect on first iteration they will break the loop and returning the
result.For long iteration its more faster than other loop (reduce,forEach)
const data = [ { "id": 123, "name": "abc" }, { "id": 456, "name": "bcd" } ]
function update(dataset,searchId,addtionObject){
let ind = dataset.findIndex(({id}) => id == searchId);
dataset[ind] = {...dataset[ind],...addtionObject}; //join the new and old array
return dataset
}
console.log(update(data,456,{status:1}))
If you want to create new state objet, you can use immer for that.
Immer will produce the nextState based on the mutations to the draft state.
import produce from "immer";
const baseState = [
{
id: 123,
name: "abc",
},
{
id: 456,
name: "bcd",
},
];
const nextState = produce(baseState, (draftState) => {
draftState[1].status = 1;
});

Javascript: Split array of objects into seperate arrays with dynamic names depending on property value

I have an array containing objects. Now I want to slice the array to new arrays containing only those objects matching a certain property value.
Ideally the new array names should be created dynamically.
The original array looks like this:
specificSlotButtonArray = [
{slotStarttime:"06:00:00", slotTimespan:1},
{slotStarttime:"09:00:00", slotTimespan:1},
{slotStarttime:"12:00:00", slotTimespan:2},
{slotStarttime:"15:00:00", slotTimespan:2},
{slotStarttime:"18:00:00", slotTimespan:3}
];
The new arrays should look like this:
timespan1 = [
{slotStarttime:"06:00:00", slotTimespan:1},
{slotStarttime:"09:00:00", slotTimespan:1}
]
timespan2 = [
{slotStarttime:"12:00:00", slotTimespan:2},
{slotStarttime:"15:00:00", slotTimespan:2}
]
timespan3 = [
{slotStarttime:"18:00:00", slotTimespan:3}
]
If possible, I want to avoid javascript syntax / functions, which are not supported by IE and some other older browsers.
I already tried to work with reduce() and slice(), but did not find a solution.
You can simply achieve your desired outcome using reduce, as you can produce an object using reduce, here's an example of how you could do it.
As you can see, it'll check that the relevant property on the object isn't null, if it is, then it's set to an empty array, after this check, it's safe to simply push the relevant values onto the array, like so.
var array = [{
slotStarttime: "06:00:00",
slotTimespan: 1
},
{
slotStarttime: "09:00:00",
slotTimespan: 1
},
{
slotStarttime: "12:00:00",
slotTimespan: 2
},
{
slotStarttime: "15:00:00",
slotTimespan: 2
},
{
slotStarttime: "18:00:00",
slotTimespan: 3
}
];
var newObject = array.reduce(function(obj, value) {
var key = `timespan${value.slotTimespan}`;
if (obj[key] == null) obj[key] = [];
obj[key].push(value);
return obj;
}, {});
console.log(newObject);
Use a generic group by key reducer. I will take it from a previous answer of mine. It is an elegant and simple way to generate a function that group your data by a particular key that comes as an argument.
const groupBy = key => (result,current) => {
const item = Object.assign({},current);
if (typeof result[current[key]] == 'undefined'){
result[current[key]] = [item];
}else{
result[current[key]].push(item);
}
return result;
};
const specificSlotButtonArray = [
{slotStarttime:"06:00:00", slotTimespan:1},
{slotStarttime:"09:00:00", slotTimespan:1},
{slotStarttime:"12:00:00", slotTimespan:2},
{slotStarttime:"15:00:00", slotTimespan:2},
{slotStarttime:"18:00:00", slotTimespan:3}
];
const timespan = specificSlotButtonArray.reduce(groupBy('slotTimespan'),{});
console.log(timespan);
You could reduce the array by taking an object for the part arrays.
var specificSlotButtonArray = [{ slotStarttime: "06:00:00", slotTimespan: 1 }, { slotStarttime: "09:00:00", slotTimespan: 1 }, { slotStarttime: "12:00:00", slotTimespan: 2 }, { slotStarttime: "15:00:00", slotTimespan: 2 }, { slotStarttime: "18:00:00", slotTimespan: 3 }],
timespan1 = [],
timespan2 = [],
timespan3 = [];
specificSlotButtonArray.reduce(function (r, o) {
r[o.slotTimespan].push(o);
return r;
}, { 1: timespan1, 2: timespan2, 3: timespan3 });
console.log(timespan1);
console.log(timespan2);
console.log(timespan3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The following soluce iterates once on specificSlotButtonArray using Array.reduce. This soluce will adapt to any number of slotTimespan.
const specificSlotButtonArray = [{
slotStarttime: '06:00:00',
slotTimespan: 1,
},
{
slotStarttime: '09:00:00',
slotTimespan: 1,
},
{
slotStarttime: '12:00:00',
slotTimespan: 2,
},
{
slotStarttime: '15:00:00',
slotTimespan: 2,
},
{
slotStarttime: '18:00:00',
slotTimespan: 3,
},
];
// Loop through the array
const splitted = specificSlotButtonArray.reduce((tmp, x) => {
// Look if we got already an array having the slotTimespan of the current
// item to treat
const match = tmp.find(y => y.some(z => z.slotTimespan === x.slotTimespan));
// If we have such array, push the data into it
if (match) {
match.push(x);
} else {
// If we don't create a new array
tmp.push([x]);
}
return tmp;
}, []);
console.log(splitted);
If you want to deals with the array straight after the Array.reduce you can use destructuration :
const [
timespan1,
timespan2,
timespan3
] = specificSlotButtonArray.reduce((tmp, x) => {
You can use this function to create separate arrays grouped by slotTimespan,
specificSlotButtonArray = [
{slotStarttime:"06:00:00", slotTimespan:1},
{slotStarttime:"09:00:00", slotTimespan:1},
{slotStarttime:"12:00:00", slotTimespan:2},
{slotStarttime:"15:00:00", slotTimespan:2},
{slotStarttime:"18:00:00", slotTimespan:3}
];
function groupBy(arr, property) {
return arr.reduce(function(memo, x) {
if (!memo[x[property]]) { memo[x[property]] = []; }
memo[x[property]].push(x);
return memo;
}, {});
}
console.log(groupBy(specificSlotButtonArray, "slotTimespan"));

Find difference between two arrays

I have following Plunkr which works perfectly.
https://plnkr.co/edit/WDjoEK7bAVpKSJbAmB9D?p=preview
It uses the _.differenceWith() function of lodash, in order two save all array values, which are not contained by the two arrays.
var result = _.differenceWith(data, test, _.isEqual);
Now I have two problems:
1.) In our project we use an older Lodash Version where the function differenceWith is not implemented
2.) I only need to compare one value of the array. This currently compares the complete objects. I only need to compare the id property.
This will find the objects in arr1 that are not in arr2 based on the id attribute.
var arr1 = [ { "id": "1" }, { "id": "2" }, { "id": "3" } ];
var arr2 = [ { "id": "1" }, { "id": "2" } ];
var result = arr1.filter(o1 => arr2.filter(o2 => o2.id === o1.id).length === 0);
console.log(result);
Note that this example does not require lodash.
If you want to use a different comparison instead of id, you can change the o2.id === o1.id part to a different property.
Here is a more generic solution:
var arr1 = [ { "name": "a" }, { "name": "b" }, { "name": "c" } ];
var arr2 = [ { "name": "a" }, { "name": "c" } ];
function differenceWith(a1, a2, prop) {
return a1.filter(o1 => a2.filter(o2 => o2[prop] === o1[prop]).length === 0);
}
var result = differenceWith(arr1, arr2, 'name');
console.log(result);

javascript filter nested object based on key value

I wish to filter a nested javascript object by the value of the "step" key:
var data = {
"name": "Root",
"step": 1,
"id": "0.0",
"children": [
{
"name": "first level child 1",
"id": "0.1",
"step":2,
"children": [
{
"name": "second level child 1",
"id": "0.1.1",
"step": 3,
"children": [
{
"name": "third level child 1",
"id": "0.1.1.1",
"step": 4,
"children": []},
{
"name": "third level child 2",
"id": "0.1.1.2",
"step": 5,
"children": []}
]},
]}
]
};
var subdata = data.children.filter(function (d) {
return (d.step <= 2)});
This just returns the unmodified nested object, even if I put value of filter to 1.
does .filter work on nested objects or do I need to roll my own function here, advise and correct code appreciated.
cjm
Recursive filter functions are fairly easy to create. This is an example, which strips a JS object of all items defined ["depth","x","x0","y","y0","parent","size"]:
function filter(data) {
for(var i in data){
if(["depth","x","x0","y","y0","parent","size"].indexOf(i) != -1){
delete data[i];
} else if (i === "children") {
for (var j in data.children) {
data.children[j] = filter(data.children[j])
}
}
}
return data;
}
If you would like to filter by something else, just updated the 2nd line with your filter function of choice.
Here's the function to filter nested arrays:
const filter = arr => condition => {
const res = [];
for (const item of arr) {
if (condition(item)) {
if (!item.children) {
res.push({ ...item });
} else {
const children = filter(item.children)(condition);
res.push({ ...item, children })
}
}
}
return res;
}
The only thing you have to do is to wrap your root object into an array to reach self-similarity. In common, your input array should look like this:
data = [
{ <...>, children: [
{ <...>, children: [...] },
...
] },
...
]
where <...> stands for some properties (in your case those are "name", "step" and "id"), and "children" is an optional service property.
Now you can pass your wrapped object into the filter function alongside a condition callback:
filter(data)(item => item.step <= 2)
and you'll get your structure filtered.
Here are a few more functions to deal with such structures I've just coded for fun:
const map = arr => f => {
const res = [];
for (const item of arr) {
if (!item.children) {
res.push({ ...f({ ...item }) });
} else {
res.push({ ...f({ ...item }), children: map(item.children)(f) });
}
}
return res;
}
const reduce = arr => g => init => {
if (!arr) return undefined;
let res = init;
for (const item of arr) {
if (!item.children) {
res = g(res)({ ...item });
} else {
res = g(res)({ ...item });
res = reduce(item.children)(g)(res);
}
}
return res;
}
Usage examples:
map(data)(item => ({ step: item.step }))
reduce(data)($ => item => $ + item.step)(0)
Likely, the code samples aren't ideal but probably could push someone to the right direction.
Yes, filter works on one array (list), like the children of one node. You have got a tree, if you want to search the whole tree you will need to use a tree traversal algorithm or you first put all nodes into an array which you can filter. I'm sure you can write the code yourself.

Categories