Trying to merge two arrays - javascript

Below is my json stored in :
$scope.regions = [];
{
"id": 100,
"regions": [
{
"id": 10,
"name": "Abc",
"rank": 0,
},
{
"id": 20,
"name": "Pqr",
"rank": 1,
},
{
"id": 30,
"name": "Lmn",
"rank": 2,
},
{
"id": 40,
"name": "xyz",
"rank": 3,
},
{
"id": 50,
"name": "GGG",
"rank": 4,
},
{
"id": 60,
"name": "YYY",
"rank": 5,
}
]
}
This is my another json stored in :
$scope.regionList = [];
var highestOrder = 3;
"regions": [
{
"id": 40,
"name": "xyz",
"rank": 0,
},
{
"id": 50,
"name": "GGG",
"rank": 1,
},
{
"id": 60,
"name": "YYY",
"rank": 2,
}
Now I want to merge $scope.regionList in to $scope.regions but for those records which are matching in both $scope.regionList and $scope.regions I would like to replace records of $scope.regions with $scope.regionList (only common records from both list).
And first non matching records from $scope.regionList will have order start using highestOrder and will keep incrementing for each non-matching records so final output will be like below :
Expected output :
"regions": [
{
"id": 10,
"name": "Abc",
"rank": 3,
},
{
"id": 20,
"name": "Pqr",
"rank": 4,
},
{
"id": 30,
"name": "Lmn",
"rank": 5,
},
{
"id": 40,
"name": "xyz",
"rank": 0,
},
{
"id": 50,
"name": "GGG",
"rank": 1,
},
{
"id": 60,
"name": "YYY",
"rank": 2,
}
As Abc is is the first non matching record so it will have order 3 and rest other will have order no from 3 i.e 4,5 6, etc.
My code:
var highestOrder = 3;
var found = false;
for (var i = 0; i < $scope.regions.length; i++) {
if ($scope.regions[i].id == 100) {
found = true;
for (var j = 0; j < $scope.regionList.length; j++) {
for (var k = 0; k < $scope.regions[i].regions.length; k++) {
if ($scope.regions[i].regions[k].id == $scope.regionList[j].id) {
$scope.regions[i].regions[k].rank = $scope.regionList[j].rank;
}
else {
$scope.regions[i].regions[k].rank = highestOrder;
highestOrder = highestOrder + 1;
}
}
}
}
if (found)
break;
}
var regions = {
"id": 100,
"regions": [{
"id": 10,
"name": "Abc",
"rank": 0,
},
{
"id": 20,
"name": "Pqr",
"rank": 1,
},
{
"id": 30,
"name": "Lmn",
"rank": 2,
},
{
"id": 40,
"name": "xyz",
"rank": 3,
},
{
"id": 50,
"name": "GGG",
"rank": 4,
},
{
"id": 60,
"name": "YYY",
"rank": 5,
}
]
}
var highestOrder = 3;
var found = false;
var regionList = [{
"id": 40,
"name": "xyz",
"rank": 0,
},
{
"id": 50,
"name": "GGG",
"rank": 1,
},
{
"id": 60,
"name": "YYY",
"rank": 2
}
]
for (var i = 0; i < regions.length; i++) {
if (regions[i].id == 100) {
found = true;
for (var j = 0; j < regionList.length; j++) {
for (var k = 0; k < regions[i].regions.length; k++) {
if (regions[i].regions[k].id == regionList[j].id) {
regions[i].regions[k].rank = regionList[j].rank;
} else {
regions[i].regions[k].rank = highestOrder;
highestOrder = highestOrder + 1;
}
}
}
}
if (found)
break;
}
console.log(regions)

You could use a hash table and build it with the elements of the array for updating.
Then iterate regions and update rank with either the hash's rank or with highestOrder. Increment highestOrder after assigning.
var $scope = { regions: [{ id: 100, regions: [{ id: 10, name: "Abc", rank: 0, }, { id: 20, name: "Pqr", rank: 1, }, { id: 30, name: "Lmn", rank: 2, }, { id: 40, name: "xyz", rank: 3, }, { id: 50, name: "GGG", rank: 4, }, { id: 60, name: "YYY", rank: 5, }] }] },
regionsUpdate = [{ id: 40, name: "xyz", rank: 0, }, { id: 50, name: "GGG", rank: 1, }, { id: 60, name: "YYY", rank: 2, }],
regionsId = 100,
highestOrder = 3,
hash = Object.create(null);
regionsUpdate.forEach(function (a) {
hash[a.id] = a;
});
$scope.regions.some(function (a) {
if (a.id === regionsId) {
a.regions.forEach(function (b) {
b.rank = hash[b.id] ? hash[b.id].rank : highestOrder++;
});
return true;
}
});
console.log($scope);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Simplify and write better a series of filter / map and reduce functions

I want to simplify and get last part missing.
So, I have an array of objects like:
const data = [{
"company": "aaa",
"qty": 1,
"id": 1
}, {
"company": "bbb",
"qty": 14,
"id": 2
}, {
"company": "ccc",
"qty": 2,
"id": 3
}, {
"company": "ddd",
"qty": 5,
"id": 4
},
{
"company": "eee",
"qty": 55,
"id": 5
}
];
I need to add the percentage based on qty and overall create a new object with detail of only companies with percentage greater than "X" (let's say 10) and others merged in a "other companies", like a similar
{
"idCompany": 2,
"name": "bbb",
"percentage": 18.181818181818183,
"qty": 14
},{
"idCompany": 5,
"name": "eee",
"percentage": 71.42857142857143,
"qty": 55
},{
"idCompany": null,
"name": "others",
"percentage": 10.391 // 100 - 71.42xxx - 18.18
"qty": 8 // 1 + 2 + 5
},
So, my steps:
Get the total:
const total = data.reduce((i, el) => i+el.qty, 0);
Add the percentage to start object:
let rawData = data.map(el => {
return {
name: el.company,
qty: el.qty,
idCompany: el.id,
percentage: (el.qty/total)*100,
}
})
Get only companies with percentage greater than X (e.g. 10)
let consolidateCompanies = rawData.filter(el => el.percentage > 10);
But...
Now, how can I get the OTHERS companies and add to the consolidateCompanies object? And with a more beautiful code?
const data = [{
"company": "aaa",
"qty": 1,
"id": 1
}, {
"company": "bbb",
"qty": 14,
"id": 2
}, {
"company": "ccc",
"qty": 2,
"id": 3
}, {
"company": "ddd",
"qty": 5,
"id": 4
},
{
"company": "eee",
"qty": 55,
"id": 5
}
];
// Get the total
const total = data.reduce((i, el) => i+el.qty, 0);
// Add percentage to the objects
let rawData = data.map(el => {
return {
name: el.company,
qty: el.qty,
idCompany: el.id,
percentage: (el.qty/total)*100,
}
})
// Get only companies that have percentage greater than 10
let consolidateCompanies = rawData.filter(el => el.percentage > 10);
console.log(consolidateCompanies);
// But I'm missing 1, 3 and 4
You could do this with a couple of Array.reduce() calls.
We'd group companies either by name or 'others', adding percentage and quantity for the others.
const data = [{ "company": "aaa", "qty": 1, "id": 1 }, { "company": "bbb", "qty": 14, "id": 2 }, { "company": "ccc", "qty": 2, "id": 3 }, { "company": "ddd", "qty": 5, "id": 4 }, { "company": "eee", "qty": 55, "id": 5 } ];
const total = data.reduce((i, el) => i+el.qty, 0);
const minPercentage = 10;
const consolidateCompanies = Object.values(data.reduce((acc, el) => {
const percentage = (el.qty/total)*100;
const name = (percentage >= minPercentage) ? el.company: 'others';
const id = (percentage >= minPercentage) ? el.id: null;
acc[name] = acc[name] || { name, percentage: 0, qty: 0, id };
acc[name].percentage += percentage;
acc[name].qty += el.qty;
return acc;
}, {}))
console.log(consolidateCompanies);
.as-console-wrapper { max-height: 100% !important; }
You could seperate others and add only if necessary without grouping by usining another object.
const
data = [{ company: "aaa", qty: 1, id: 1 }, { company: "bbb", qty: 14, id: 2 }, { company: "ccc", qty: 2, id: 3 }, { company: "ddd", qty: 5, id: 4 }, { company: "eee", qty: 55, id: 5 }],
totalQty = data.reduce((t, { qty }) => t + qty, 0),
others = { idCompany: null, name: "others", percentage: 0, qty: 0 },
result = data.reduce((r, { company: name, qty, id: idCompany }) => {
const percentage = qty * 100 / totalQty;
if (percentage >= 10) {
r.push({ idCompany, name, percentage, qty });
} else {
others.qty += qty;
others.percentage = others.qty * 100 / totalQty;
}
return r;
}, []);
if (others.qty) result.push(others);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
here is the refactor of your code
const data = [{
"company": "aaa",
"qty": 1,
"id": 1
}, {
"company": "bbb",
"qty": 14,
"id": 2
}, {
"company": "ccc",
"qty": 2,
"id": 3
}, {
"company": "ddd",
"qty": 5,
"id": 4
},
{
"company": "eee",
"qty": 55,
"id": 5
}
];
const total = data.reduce((i, el) => i+el.qty, 0)
const startWith = [{
"idCompany": null,
"name": "others",
"percentage": 0,
"qty": 0
}]
let rawData = data.reduce((acc, value) => {
// console.log('value', value)
const withPercentage = {
name: value.company,
qty: value.qty,
idCompany: value.id,
percentage: (value.qty/total)*100,
}
if (withPercentage.percentage > 10) {
acc.push(withPercentage)
} else {
acc[0].qty += withPercentage.qty;
acc[0].percentage += withPercentage.percentage;
}
return acc;
}, startWith).filter(f => f.percentage !== 0 && f.qty !== 0);
console.log(rawData)
Use forEach instead of map and filter, and loop the Object with your required need as follows.
const minPerc =10;
const data = [{
"company": "aaa",
"qty": 1,
"id": 1
}, {
"company": "bbb",
"qty": 14,
"id": 2
}, {
"company": "ccc",
"qty": 2,
"id": 3
}, {
"company": "ddd",
"qty": 5,
"id": 4
},
{
"company": "eee",
"qty": 55,
"id": 5
}
];
// Get the total
const total = data.reduce((i, el) => i+el.qty, 0);
let consolidateCompanies = [];
let otherObj={"idCompany": null,
"name": "others",
"percentage": 0,
"qty": 0};
data.forEach(ele=>{
let perc=(ele.qty/total)*100;
if(perc>minPerc){
consolidateCompanies.push({...ele,...{percentage:perc}})
}else{
otherObj.qty +=ele.qty; otherObj.percentage+=perc
} });
consolidateCompanies.push(otherObj);
console.log(consolidateCompanies);

Javascript creates new arrays based on conditions

I have old and entity arrays:
var old = [
{
"id": 3,
"entity_type_id": 1,
"product_id": 4,
"name": "test1",
"acreage": 100,
"yield": 20,
"worth": 30
},
{
"id": 4,
"entity_type_id": 1,
"product_id": 4,
"name": "test2",
"acreage": 10,
"yield": 20,
"worth": 0
},
{
"id": 5,
"entity_type_id": 3,
"product_id": 5,
"name": "test3",
"acreage": 20,
"yield": 20,
"worth": 40
}
]
var entity = [
{"id": 1, "name": "a1"},
{"id": 2, "name": "a2"},
{"id": 3, "name": "a3"}
]
I hope to get the following data:
var newArr = [
{
"id": 3,
"entity_type_id": 1,
"product_id": 4,
"name": "test1",
"acreage": 110,
"yield": 40,
"worth": 30,
"entity_type_1": 2, // The total amount of entity_type_id (entity_type_id: 1)
"entity_type_2": 0,
"entity_type_3": 0
},
{
"id": 5,
"entity_type_id": 3,
"product_id": 5,
"name": "test3",
"acreage": 20,
"yield": 20,
"worth": 40,
"entity_type_1": 0,
"entity_type_2": 0,
"entity_type_3": 1 // The total amount of entity_type_id (entity_type_id: 3)
}
]
console.log(newArr)
I tried the following code and got some data. I'm not sure if there will be any exceptions or errors.
What's more, I don't know how to deal with the entity array data. Can someone help me solve this problem and get the result I expect?
Thank you very much !
function mergeArr(arr) {
const temp = []
arr.forEach((dataItem) => {
if (temp.length) {
let filterValue = temp.filter((items) => {
return items.product_id === dataItem.product_id
})
if (filterValue.length) {
temp.forEach((n) => {
if (n.product_id === filterValue[0].product_id) {
n.yield = dataItem.yield + filterValue[0].yield
n.acreage = dataItem.acreage + filterValue[0].acreage
n.worth = dataItem.worth + filterValue[0].worth
}
})
} else {
temp.push(dataItem)
}
} else {
temp.push(dataItem)
}
})
return temp
}
Youi could find the object and sum the wanted properties. For entity take another loop and map new entries and build a new object from it for spreading.
var old = [{ id: 3, entity_type_id: 1, product_id: 4, name: "test1", acreage: 100, yield: 20, worth: 30 }, { id: 4, entity_type_id: 1, product_id: 4, name: "test2", acreage: 10, yield: 20, worth: 0 }, { id: 5, entity_type_id: 3, product_id: 5, name: "test3", acreage: 20, yield: 20, worth: 40 }],
entity = [{ id: 1, name: "a1" }, { id: 2, name: "a2" }, { id: 3, name: "a3" }],
entityTypes = Object.fromEntries(entity.map(({ id }) => ['entity_type_' + id, 0])),
result = old.reduce((r, o) => {
let temp = r.find(q => q.product_id === o.product_id);
if (!temp) r.push(temp = { ... o, ...entityTypes });
else ['acreage', 'yield', 'worth'].forEach(k => temp[k] += o[k]);
temp['entity_type_' + o.entity_type_id]++;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to get immediate parent Id of the child id in array of nested json Object?

I need to get the parent id of the specific child.
Here is my sample JSON, If I give entity id 32 it should return 6 as parent Id and if I give 30 it should return 5 as parent id.
const arr = [{
"id": 0,
"name": "My Entity",
"children": [
{
"id": 1,
"name": "MARKET",
"children": [
{
"id": 2,
"name": "Sales",
"children": [
{
"id": 3,
"name": "District 1",
"children": [
{
"id": 5,
"name": "Area 1",
"children": [
{
"entityId": 30,
"id": 26,
"name": "Mumbai"
},
{
"entityId": 31,
"id": 26,
"name": "Hyderabad"
}
],
"num": 0,
},
{
"id": 6,
"name": "Area 2",
"children": [
{
"entityId": 32,
"id": 32,
"name": "Karnataka"
},
{
"entityId": 33,
"id": 33,
"name": "Andhra Pradesh"
}
],
"num": 0,
},
]
},
]
},
]
},
]
}]
Here is the code I have tried
const findParent = (arr, entityId) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].entityId === entityId) {
return [];
} else if (arr[i].children && arr[i].children.length) {
const t = findParents(arr[i].children, entityId);
if (t !== false) {
t.push(arr[i].id);
return t;
}
}
}
return false;
};
findParents(arr, 30);
But it is returning like below
[
5,
3,
2,
1,
0
]
But I want the output to be
[
5
]
Kindly help me on this, thanks
Replace this:
t.push(arr[i].id);
with:
if (t.length == 0) t.push(arr[i].id);
I would suggest easier solution
const findParent = (arr, entityId) => {
const children = arr.flatMap(parent =>
(item.children || []).map(child => ({ parent, child, entityId: child.entityId }))
)
const res = children.find(item => item.entityId === entityId)
return res.entityId || findChildren(res.map(v => v.child), entityId)
}

Group array of nested objects with multiple levels in JavaScript

I'm trying to group a big nested object with multiple properties such as this one :
[
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 70,
"name": "Name70"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 61,
"name": "Name61"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 29,
"name": "Name29"
}
}
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 8,
"name": "Name8",
"sub": {
"id": 163,
"name": "Name163"
}
}
}
}
},
{
"id": 10,
"name": "Name10",
"sub": {
"id": 4,
"name": "Name4"
}
}
]
As you can see, the "sub" are not arrays as of now, but they would be in the expected output even if there's only one object in it.
I'd like to group the array by object's id recursively to get this kind of output :
[
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": [
{
"id": 70,
"name": "Name70"
},
{
"id": 61,
"name": "Name61"
},
{
"id": 4,
"name": "Name4",
"sub": [
{
"id": 5,
"name": "Name5",
"sub": [
{
"id": 29,
"name": "Name29"
},
{
"id": 8,
"name": "Name8",
"sub": [
{
"id": 163,
"name": "Name163"
}
]
}
]
}
]
}
]
},
{
"id": 10,
"name": "Name10",
"sub": [
{
"id": 4,
"name": "Name4"
}
]
}
]
So far, I tried some shenanigans with lodash and d3.nest() but I just can't seem to group it.
Have you guys ever face something similar? And if so, how did you manage to code this?
Thanks a lot
You could take a recursive approach with a function which merges an object into an array by looking for same id.
const
merge = (target, { sub, ...o }) => {
let temp = target.find(({ id }) => id === o.id);
if (sub) sub = merge(temp?.sub || [], sub)
if (!temp) target.push(temp = { ...o, sub });
return target;
};
var data = [{ id: 14, name: "Name14", theme: true, sub: { id: 70, name: "Name70" } }, { id: 14, name: "Name14", theme: true, sub: { id: 61, name: "Name61" } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 29, name: "Name29" } } } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 8, name: "Name8", sub: { id: 163, name: "Name163" } } } } }, { id: 10, name: "Name10", sub: { id: 4, name: "Name4" } }],
result = data.reduce(merge, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could create a Map for each sub property -- keyed by id -- and merge the objects into those maps. Then finally convert those sub-maps back to sub-arrays:
function mergeArray(arr) {
function mergeObject(a, b) {
while (b = b.sub) {
if (!a.sub) a.sub = new Map;
let child = a.sub.get(b.id);
if (child) a = child;
else a.sub.set(b.id, a = { id: b.id, name: b.name });
}
}
function convertMap(map) {
return Array.from(map.values(), obj => {
if (obj.sub) obj.sub = convertMap(obj.sub);
return obj;
});
}
let map = new Map(arr.map(({id, name}) => [id, {id, name}]));
for (let item of arr) mergeObject(map.get(item.id), item);
return convertMap(map);
}
// Demo with input from question
let input = [{"id": 14,"name": "Name14","theme": true,"sub": {"id": 70,"name": "Name70"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 61,"name": "Name61"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 29,"name": "Name29"}}}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 8,"name": "Name8","sub": {"id": 163,"name": "Name163"}}}}},{"id": 10,"name": "Name10","sub": {"id": 4,"name": "Name4"}}];
console.log(mergeArray(input));
I would do it with recursive functions, because I think those are more versatile:
const data = [{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 70,
"name": "Name70"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 61,
"name": "Name61"
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 29,
"name": "Name29"
}
}
}
},
{
"id": 14,
"name": "Name14",
"theme": true,
"sub": {
"id": 4,
"name": "Name4",
"sub": {
"id": 5,
"name": "Name5",
"sub": {
"id": 8,
"name": "Name8",
"sub": {
"id": 163,
"name": "Name163"
}
}
}
}
},
{
"id": 10,
"name": "Name10",
"sub": {
"id": 4,
"name": "Name4"
}
}
]
// setting up arrays
const recursiveModify = (node) => {
if (typeof node.sub === "undefined") {
return node
} else {
node.sub = [recursiveModify(node.sub)]
return node
}
}
const recursiveReduce = (nodes) => {
return nodes.reduce((a, c) => {
const item = a.find(e => e.id === c.id)
if (!item) {
a.push(c)
} else {
item.sub = recursiveReduce([...item.sub, ...c.sub])
}
return a
}, [])
}
const dataWithArray = data.map(node => {
return recursiveModify(node)
})
const result = recursiveReduce(dataWithArray)
console.log(result)
Unfortunately I could only do it with two passes - one for creating the sub as arrays, then one for actually grouping the data. I'm pretty sure it can be done in one pass - I just have no more time to work it out.

Sorting JSON alphabetically by first letter

I have a JSON like this:
[
{
"id": 1,
"slug": "abakan",
"name": "Абакан"
},
{
"id": 4,
"slug": "almetevsk",
"name": "Альметьевск"
},
{
"id": 10,
"slug": "astrahan",
"name": "Астрахань"
},
{
"id": 11,
"slug": "barnaul",
"name": "Барнаул"
},
...
]
And getting this by this method:
public function getCities()
{
$cities = City::mainCities()->get(['id', 'slug', 'name']);
return response()->json($cities);
}
How can i sort this list alphabetically and with their letters. For example:
"A": [
{
"id": 1,
"slug": "abakan",
"name": "Абакан"
},
{
"id": 4,
"slug": "almetevsk",
"name": "Альметьевск"
}
],
"B": [
{
"id": 11,
"slug": "barnaul",
"name": "Барнаул"
},
...
]
and so on...
I have Laravel on the backend and VueJS on front.
My solution:
var cities = [
{ id: 1, slug: "abakan", name: "Абакан" },
{ id: 4, slug: "almetevsk", name: "Альметьевск" },
{ id: 11, slug: "barnaul", name: "Барнаул" },
{ id: 10, slug: "astrahan", name: "Астрахань" }
];
cities.sort(function(a, b) {
return a.slug[0].localeCompare(b.slug[0]);
});
var newCities = {};
for (var i = 0; i < cities.length; i++) {
var c = cities[i].slug[0].toUpperCase();
if (newCities[c] && newCities[c].length >= 0)
newCities[c].push(cities[i]);
else {
newCities[c] = [];
newCities[c].push(cities[i]);
}
}
console.log(newCities);
This works for me:
var items = [
{
"id": 11,
"slug": "barnaul",
"name": "Барнаул"
},
{
"id": 1,
"slug": "abakan",
"name": "Абакан"
},
{
"id": 4,
"slug": "almetevsk",
"name": "Альметьевск"
},
{
"id": 10,
"slug": "astrahan",
"name": "Астрахань"
}
];
var sortedItems = items.sort((a, b) => a.slug.localeCompare(b.slug));
var results = {};
for (var i = 0; i < 26; i++) {
var char = String.fromCharCode(97 + i);
var bigChar = char.toUpperCase();
results[bigChar] = [];
for (var s = 0; s < sortedItems.length; s++) {
if (sortedItems[s].slug.startsWith(char)) {
results[bigChar].push(sortedItems[s]);
}
}
}
console.log(results)

Categories

Resources