String-path to Tree (JavaScript) - javascript

I have an array of paths in string format like that:
[
{ _id: 'women/clothes/tops', count: 10 },
{ _id: 'women/clothes/suits', count: 5 },
{ _id: 'women/accessories', count: 2 },
{ _id: 'men/clothes', count: 1 },
]
I would like to group them into a tree structure like that:
[
{
_id: 'women',
count: 17,
children: [
{
_id: 'clothes',
count: 15,
children: [
{ _id: 'tops', count: 10 },
{ _id: 'suits', count: 5 }
]
},
{
_id: 'accessories',
count: 2
}
]
},
{
_id: 'men',
count: 1,
children: [
{
_id: 'clothes',
count: 1
}
]
}
]
I would imagine a sort of recursive function calling a reduce method. But I can't figure how exactly.
EDIT :
I managed to get close with this solution. But I still get an empty object key, and i cannot manage to not have the children key when there are no children:
const getTree = (array) => {
return array.reduce((a, b) => {
const items = b._id.replace('\/', '').split('/')
return construct(a, b.count, items)
}, {})
}
const construct = (a, count, items) => {
const key = items.shift()
if(!a[key]) {
a[key] = {
_id: key,
count: count,
children: []
}
a[key].children = items.length > 0 ? construct(a[key].children, count, items) : null
}
else {
a[key].count += count
a[key].children = items.length > 0 ? construct(a[key].children, count, items) : null
}
return a
}

I created an object tree first and then converted that to your array of objects with children structure.
Note: I used a _count property on each object in the intermediate structure so that when looping over the keys later (when creating the final structure), I could ignore both _id and _count easily, and loop over only the "real children" keys, which don't start with _.
I did not look at your current attempt/solution before writing this, so mine looks quite different.
const origData = [
{ _id: 'women/clothes/tops', count: 10 },
{ _id: 'women/clothes/suits', count: 5 },
{ _id: 'women/accessories', count: 2 },
{ _id: 'men/clothes', count: 1 },
];
const newObj = {};
for (let obj of origData) {
//console.log(obj)
const tempArr = obj._id.split('/');
let tempHead = newObj; // pointer
for (let idx in tempArr) {
let head = tempArr[idx];
if (!tempHead.hasOwnProperty(head)) {
tempHead[head] = {};
}
tempHead = tempHead[head];
tempHead._id = head;
const currCount = tempHead._count || 0;
tempHead._count = currCount + obj.count;
}
tempHead._count = obj.count;
}
console.log(newObj);
const finalArr = [];
let tempArrHead = finalArr; // pointer
let tempObjHead = newObj; // pointer
function recursiveStuff(currObj, currArr, copyObj) {
let hasChildren = false;
const keys = Object.keys(currObj).filter(a => !a.startsWith("_"));
for (let key of keys) {
hasChildren = true;
const obj = {
_id: currObj[key]._id,
count: currObj[key]._count || 0,
children: [],
};
currArr.push(obj);
recursiveStuff(currObj[key], obj.children, obj)
}
if (hasChildren == false) {
// console.log(copyObj);
// there might be a more elegant way, but this works:
delete copyObj.children;
}
}
recursiveStuff(tempObjHead, tempArrHead)
console.log(finalArr);
.as-console-wrapper{
max-height: 100% !important;
}
Intermediate Structure:
{
"women": {
"_id": "women",
"_count": 17,
"clothes": {
"_id": "clothes",
"_count": 15,
"tops": {
"_id": "tops",
"_count": 10
},
"suits": {
"_id": "suits",
"_count": 5
}
},
"accessories": {
"_id": "accessories",
"_count": 2
}
},
"men": {
"_id": "men",
"_count": 1,
"clothes": {
"_id": "clothes",
"_count": 1
}
}
}
Final Structure:
[
{
"_id": "women",
"count": 17,
"children": [
{
"_id": "clothes",
"count": 15,
"children": [
{"_id": "tops", "count": 10},
{"_id": "suits", "count": 5}
]
},
{"_id": "accessories", "count": 2}
]
},
{
"_id": "men",
"count": 1,
"children": [
{"_id": "clothes", "count": 1}
]
}
]

Related

JavaScript - filter in loop, create duplicate

I want to filter an array with another array in order to know if there are new people.
const people = [
{ "name": "jerry" },
{ "name": "tom" },
{ "name": "alex" }
]
const newList = [
{ "name": "bran" },
{ "name": "jerry" },
{ "name": "john" }
]
const new_people = []
for (const pp of people) {
let result = newList.filter(newL => newL.name != pp.name)
if (result) {
new_people.push(result)
}
}
console.log(new_people)
This is the result:
[
[ { name: 'bran' }, { name: 'john' } ],
[ { name: 'bran' }, { name: 'jerry' }, { name: 'john' } ],
[ { name: 'bran' }, { name: 'jerry' }, { name: 'john' } ]
]
But I'm looking for:
[ { name: 'bran' }, { name: 'john' } ]
I would like to avoid the loop because it makes duplicate in the result but I don't know how I can't do it without the loop.
First make a temporary array of people name:
const peopleNames = people.map(pp => pp.name);
Then the peopleNames will be as follows:
['jerry', 'tom', 'alex']
Now filter the new people from the newList:
const newPeople = newList.filter(pp => !peopleNames.includes(pp.name));
The newPeople will be an array of objects that you are looking for.
[{name: 'bran'}, {name: 'john'}]
const people = [
{ "name": "jerry" },
{ "name": "tom" },
{ "name": "alex" }
]
const newList = [
{ "name": "bran" },
{ "name": "jerry" },
{ "name": "john" }
]
const output = newList.filter(a => people.filter(x => x.name == a.name).length == 0);
console.log('By USing Filter', output);
//filter the newList and retain those object, which are not present in the people
//Way 2: By using some
//const output2 = newList.filter(a => !people.some(x => x.name == a.name));
//console.log('By Using Some', output2);
You can use Array's reduce method to get the desired result:
const new_people = newList.reduce((accVal, e) =>
(people.map(p => p.name).includes(e.name))
? accVal
: accVal.concat({ "name": e.name } ),
[ ] )

Remove JSON attributes if not found in defined array

I need to remove attribute from the meta if it's not exist in att
for example : cardNo is not existing in the att
const att = ['id', 'name','class'];
const meta = [
{
"id": 1,
"name": "test",
"cardNo": 23
},
{
"id": 2,
"name": "test2",
"cardNo": 232
}
];
Expected output:
[
{
"id": 1,
"name": "test",
},
{
"id": 2,
"name": "test2"
}
];
for(let data of meta){
for (let key of Object.keys(data)) {
if (arr.indexOf(key) == -1) {
delete obj[key];
}
} }
Use Ramda.js (https://ramdajs.com/docs/) to make it easier:
const att = ['id', 'name','class'];
const meta = [
{
"id": 1,
"name": "test",
"cardNo": 23
},
{
"id": 2,
"name": "test2",
"cardNo": 232
}
]
const onlyAtts = map(pick(att), meta)
i think this is the answer for your question.
const att = ['id', 'name','class'];
const meta = [
{
"id": 1,
"name": "test",
"cardNo": 23
},
{
"id": 2,
"name": "test2",
"cardNo": 232
}
];
let newMeta = meta.map((d) => {
let obj = {};
att.forEach((currentAtt) => {
if(d.hasOwnProperty(currentAtt)) {
obj[currentAtt] = d[currentAtt];
}
});
return obj;
});
console.log(newMeta);
You can easily achieve this using map and reduce.
const att = ["id", "name", "class"];
const meta = [
{
id: 1,
name: "test",
cardNo: 23,
},
{
id: 2,
name: "test2",
cardNo: 232,
},
];
const result = meta.map((obj) => {
const perfectObj = att.reduce((acc, curr) => {
if (obj[curr]) {
acc[curr] = obj[curr];
}
return acc;
}, {});
return perfectObj;
});
console.log(result);
This is also works
const att = ['id', 'name','class'];
const meta = [
{
"id": 1,
"name": "test",
"cardNo": 23
},
{
"id": 2,
"name": "test2",
"cardNo": 232
}
];
function compare(meta, fields) {
meta.map(object => {
let dataKeys = Object.keys(object);
dataKeys.forEach(element => {
if(fields.indexOf(element) < 0) {
delete object[element];
}
});
});
}
compare(meta, att);
console.log(meta);

Combining same objects in json array javascript

I have this array:
[{ "id": 1, "myId": "100", "name": "amey" }, { "id": 2, "myId": "100", "name": "anuj" }, { "id": 3, "myId": "101", "name": "suraj" }, { "id": 4, "myId": "101", "name": "suraj h" }]
I want output like this:
[{ "id": 1, "myId": "100", "name": ["amey", "anuj"] }, { "id": 3, "myId": "101", "name": ["suraj", "suraj h] }]
How can I do this using javascript
for (var i = 0; i < myarray.length; i++) {
//And loop again for duplicate data
for (var j = i + 1; j < myarray.length; j++) {
if (
myarray[i].VENDOR_ID == myarray[j].VENDOR_ID &&
myarray[i].ORDER_ID === myarray[j].ORDER_ID
) {
var tmp = myarray[j].NAME;
console.log(tmp);
myarray[j].NAME = [];
myarray[j].NAME.push(tmp);
myarray[j].NAME.push(myarray[i].NAME);
myarray[i] = {};
}
}
}
You can use an array reduce into an object and return the array of values. Reduce into an object using the myId property as the key to group by. Shallow copy any existing state and and name array, appending the new name value from the current element.
Object.values(
input.reduce(
(acc, { id, myId, name }) => ({
...acc,
[myId]: {
...(acc[myId] || { id, myId }),
name: [...(acc[myId]?.name || []), name]
}
}),
{}
)
const input = [
{ id: 1, myId: "100", name: "amey" },
{ id: 2, myId: "100", name: "anuj" },
{ id: 3, myId: "101", name: "suraj" },
{ id: 4, myId: "101", name: "suraj h" }
];
const res = Object.values(
input.reduce(
(acc, { id, myId, name }) => ({
...acc,
[myId]: {
...(acc[myId] || { id, myId }),
name: [...(acc[myId]?.name || []), name]
}
}),
{}
)
);
console.log(JSON.stringify(res));
You can use Array.prototype.reduce():
const arr1 = [{
"id": 1,
"myId": "100",
"name": "amey"
}, {
"id": 2,
"myId": "100",
"name": "anuj"
}, {
"id": 3,
"myId": "101",
"name": "suraj"
}, {
"id": 4,
"myId": "101",
"name": "suraj h"
}]
const reduced = arr1.reduce((acc, item) => {
// 1. check if the 'acc' array already contains an item with the same 'myId' attribute
const itemIndex = acc.findIndex(it => it.myId === item.myId);
// 2. if there isn't any, push into the 'acc' array a copy of the item,
// with the 'name' property converted into an array of strings
// otherwise simply push the 'name' into the already existing item
if (itemIndex === -1) {
acc.push({
...item,
name: [item.name]
});
} else {
acc[itemIndex].name.push(item.name)
}
return acc;
}, []);
// test
console.log(reduced);

Javascript array equality control and changing value

Hi I have two changeable length arrays and I tried If there is no value I want, delete it from that array and change the sum value if it has changed array 2 same serials code
array1 = [
{
"serial": "3",
"sum": "1"
},
{
"serial": "700",
"sum": "2"
},
{
"serial": "300",
"sum": "1"
},
]
array2 = [{
"someting": 10,
"sum": "3",
"serialList": ["700","711"],
},
{
"someting": 10,
"sum": "1",
"serialList": ["300"],
},
{
"someting": 10,
"sum": "2",
"serialList": [],
}
]
his my two array as I said arrays length is changeable sometimes array1 length big, sometimes array2 and I want If serial number in array1 does not exist in array2 delete from array1 element and change the sum value if it has changed array2 same serials code, according to above array1[0] serial codes does not exist and array1[1] sum value different array2[0] sum value change to sum value array1[1] to array2[0], serial number 300 to same sum number to array don't do anything I want to output array1 is:
array1 = [
{
"serial": "700",
"sum": "3"
},
{
"serial": "300",
"sum": "1"
},
]
Using a flatMap
array1.flatMap(el => {
// find array2 element with array1 element's serial
const array2el = array2.find(({ serialList }) =>
serialList.includes(el.serial)
);
if (array2el) {
if (array2el.sum !== el.sum) {
el.sum = array2el.sum; // sum different, update
}
} else {
return []; // return [] to delete
}
return [el]; // return [el] to keep
});
const array1 = [
{
serial: "3",
sum: "1"
},
{
serial: "700",
sum: "2"
},
{
serial: "300",
sum: "1"
}
];
const array2 = [
{
someting: 10,
sum: "3",
serialList: ["700", "711"]
},
{
someting: 10,
sum: "1",
serialList: ["300"]
},
{
someting: 10,
sum: "2",
serialList: []
}
];
const processedArray1 = array1.flatMap(el => {
const array2el = array2.find(({ serialList }) =>
serialList.includes(el.serial)
);
if (array2el) {
if (array2el.sum !== el.sum) {
el.sum = array2el.sum;
}
} else {
return []; // delete
}
return [el]; // return el
});
console.log(processedArray1);
Using a reduce
const processedArray1 = array1.reduce((acc, el) => {
// find array2 element with array1 element's serial
const array2el = array2.find(({ serialList }) =>
serialList.includes(el.serial)
);
if (array2el) {
if (array2el.sum !== el.sum) {
el.sum = array2el.sum; // sum different, update
}
acc.push(el); // push into filtered array if found in array2
}
return acc;
}, []);
const array1 = [
{
serial: "3",
sum: "1"
},
{
serial: "700",
sum: "2"
},
{
serial: "300",
sum: "1"
}
];
const array2 = [
{
someting: 10,
sum: "3",
serialList: ["700", "711"]
},
{
someting: 10,
sum: "1",
serialList: ["300"]
},
{
someting: 10,
sum: "2",
serialList: []
}
];
const processedArray1 = array1.reduce((acc, el) => {
const array2el = array2.find(({ serialList }) =>
serialList.includes(el.serial)
);
if (array2el) {
if (array2el.sum !== el.sum) {
el.sum = array2el.sum;
}
acc.push(el)
}
return acc;
}, []);
console.log(processedArray1);

How to group from array object

I using code form "
I am looking for best ways of doing this. I have group:
data
[
{
"date": "16/04/2020",
"count": 0,
"name": "A"
},
{
"date": "16/04/2020",
"count": 1,
"name": "B"
},
{
"date": "17/04/2020",
"count": 0,
"name": "B"
}
//...More.....
]
Answer
{
"date": "04/2020",
"symtom": {
"data": [
{
"date": "16/04/2020",
"data": [
{
"name": "A",
"count": [
{
"date": "16/04/2020",
"count": 0,
"name": "A"
}
]
},
{
"name": "B",
"count": [
{
"date": "16/04/2020",
"count": 1,
"name": "B"
}
]
},
//...More.....
]
},
{
"date": "17/04/2020",
"data": [
{
"name": "B",
"count": [
{
"date": "17/04/2020",
"count": 0,
"name": "B"
}
]
},
//...More.....
]
}
]
}
}
Can I fix the code and to get the desired answer?
Code :
const items = [
{
tab: 'Results',
section: '2017',
title: 'Full year Results',
description: 'Something here',
},
{
tab: 'Results',
section: '2017',
title: 'Half year Results',
description: 'Something here',
},
{
tab: 'Reports',
section: 'Marketing',
title: 'First Report',
description: 'Something here',
}
];
function groupAndMap(items, itemKey, childKey, predic){
return _.map(_.groupBy(items,itemKey), (obj,key) => ({
[itemKey]: key,
[childKey]: (predic && predic(obj)) || obj
}));
}
var result = groupAndMap(items,"tab","sections",
arr => groupAndMap(arr,"section", "items"));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
ref : Group array of object nesting some of the keys with specific names
But I would like to have the answer line this (Answer) :
{
"date": "04/2020",
"symtom": {
"data": [
{
"date": "16/04/2020",
"data": [
{
"name": "A",
"count": 0,
},
{
"name": "B",
"count": 1,
},
//...More.....
]
},
{
"date": "17/04/2020",
"data": [
{
"name": "B",
"count":0,
},
//...More.....
]
}
]
}
}
Thanks!
I am a beginner but it looks like you want system.data.data to = an array of objects with the keys name:str and count:number but instead you are applying the whole object into count so the key count:{name:A, count:0,date:etc}.
I really can't follow your function which separates the data... but all you should have to do is when count is sent the object to reference just do a dot notation like object.count to access the number vs the object that way you will have the desired affect. Hopefully that is what you were asking.
I would use a helper function groupBy (this version is modeled after the API from Ramda [disclaimer: I'm one of its authors], but it's short enough to just include here.) This takes a function that maps an object by to a key value, and then groups your elements into an object with those keys pointing to arrays of your original element.
We need to use that twice, once to group by month and then inside the results to group by day. The rest of the transform function is just to format your output the way I think you want.
const groupBy = (fn) => (xs) =>
xs .reduce((a, x) => ({... a, [fn(x)]: [... (a [fn (x)] || []), x]}), {})
const transform = (data) =>
Object .entries (groupBy (({date}) => date.slice(3)) (data)) // group by month
.map (([date, data]) => ({
date,
symtom: {
data: Object .entries (groupBy (({date}) => date) (data)) // group by day
.map (([date, data]) => ({
date,
data: data .map (({date, ...rest}) => ({...rest})) // remove date property
}))
}
}))
const data = [{date: "16/04/2020", count: 0, name: "A"}, {date: "16/04/2020", count: 1, name: "B"}, {date: "17/04/2020", count: 0, name: "B"}, {date: "03/05/2020", count: 0, name: "C"}];
console .log (
transform (data)
)
.as-console-wrapper {min-height: 100% !important; top: 0}
If you need to run in an environment without Object.entries, it's easy enough to shim.
You could take a function for each nested group and reduce the array and the grouping levels.
var data = [{ date: "16/04/2020", count: 0, name: "A" }, { date: "16/04/2020", count: 1, name: "B" }, { date: "17/04/2020", count: 0, name: "B" }],
groups = [
(o, p) => {
var date = o.date.slice(3),
temp = p.find(q => q.date === date);
if (!temp) p.push(temp = { date, symptom: { data: [] } });
return temp.symptom.data;
},
({ date }, p) => {
var temp = p.find(q => q.date === date);
if (!temp) p.push(temp = { date, data: [] });
return temp.data;
},
({ date, ...o }, p) => p.push(o)
],
result = data.reduce((r, o) => {
groups.reduce((p, fn) => fn(o, p), r);
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources