NodeJS Json sort by child array - javascript

I want to sort my object by it´s child array.
let partners = [
{
Name: "A",
Sectors: [
{ Code: "AA1", Total: 10 },
{ Code: "AA2", Total: 70 }
]
},
{
Name: "B",
Sectors: [
{ Code: "AA1", Total: 20 },
{ Code: "AA2", Total: 60 }
]
},
{
Name: "C",
Sectors: [
{ Code: "AA1", Total: 40 },
{ Code: "AA2", Total: 50 }
]
}
];
Lets say I want to sort by the prop Sectors where Code = AA1 DESC. The result (prop Name) would be:
C, B, A
Or if I need Code = AA2 DESC, would be A, B, C
I had some dificults with a simple sort like this:
let sorted = partners.sort((a, b) => a.Sectors.Total - b.Sectors.Total);
sorted.forEach((sortedData) => console.log(sortedData));
Here is a SandBox
Here is the correct answer with a change to acept empty.
let partners = [
{"Name":"E","Sectors": null},
{"Name":"A","Sectors":[{"Code":"AA1","Total":10},{"Code":"AA2","Total":70}]},
{"Name":"B","Sectors":[{"Code":"AA1","Total":20},{"Code":"AA2","Total":60}]},
{"Name":"C","Sectors":[{"Code":"AA1","Total":40},{"Code":"AA2","Total":50}]},
{"Name":"D","Sectors": null},
{"Name":"P","Sectors": []}
]
partners.sort((a, b) => {
const get = (arr, key, val) => {
return arr.find(o => o[key] === val) || {};
}
let sA = {}, sB = {}
try {
sA = get(a.Sectors, 'Code', 'AA1')
sB = get(b.Sectors, 'Code', 'AA1')
} catch (e) {}
return !!sB.Total - !!sA.Total || sB.Total - sA.Total
})
console.log(partners)

You could to this with sort and find methods. Inside the sort you need to find element from the Sectors array with specific Code and then compare the Total values.
let partners = [{"Name":"A","Sectors":[{"Code":"AA1","Total":10},{"Code":"AA2","Total":70}]},{"Name":"B","Sectors":[{"Code":"AA1","Total":20},{"Code":"AA2","Total":60}]},{"Name":"C","Sectors":[{"Code":"AA1","Total":40},{"Code":"AA2","Total":50}]}]
partners.sort((a, b) => {
const sA = a.Sectors.find(({ Code }) => Code === 'AA1');
const sB = b.Sectors.find(({ Code }) => Code === 'AA1');
return sB.Total - sA.Total
})
console.log(partners)

You could take a dynamic approach and find the wanted value for sorting.
const
sortBy = (fn, order) => (a, b) => (order === 'ASC' || -1) * (fn(a) - fn(b)),
getValue = code => o => o.Sectors.find(({ Code }) => Code === code)?.Total || 0,
partners = [{ Name: "A", Sectors: [{ Code: "AA1", Total: 10 }, { Code: "AA2", Total: 70 }] }, { Name: "B", Sectors: [{ Code: "AA1", Total: 20 }, { Code: "AA2", Total: 60 }] }, { Name: "C", Sectors: [{ Code: "AA1", Total: 40 }, { Code: "AA2", Total: 50 }] }];
console.log(partners.sort(sortBy(getValue('AA1'), 'DESC')));
console.log(partners.sort(sortBy(getValue('AA2'), 'DESC')));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Sort Array Object in Array Object javascript

how to sort highest value in this data using javascript?
data = [{a: [{num:31}, {num:10}]},{a: [{num:4}, {num:9}]},{a: [{num:5}, {num:9}]}]
Expected
data = [{a: [{num:31}]},{a: [{num:9}]},{a: [{num:9}]}]
I try like this but never happen :)
const data_sort = data.sort((a, b) => {
let abc
if (a.a.length > 0) {
abc = a.a.sort((x, y) => x.a - x.a);
}
return a - b
})
let data = [{a: [{num:31}, {num:10}]},{a: [{num:4}, {num:9}]},{a: [{num:5}, {num:9}]}]
data = data.map(item => ({a:[item.a.sort((a, b) => b.num-a.num)[0]]})).sort((a, b) => b.a[0].num-a.a[0].num)
console.log(data)
Assuming this is the correct syntax, all you need is to map every item in the array to it's largest item, then sort that.
var data = [{
a: [{num:31}, {num:10}]
}, {
a: [{num:4}, {num:9}]
}, {
a: [{num:6}, {num:11}]
}, {
a: [{num:5}, {num:9}]
}];
var result = data.map(function(item) {
return {
a: [item.a.sort(function(a,b) {
return a.num - b.num
}).reverse()[0]]
};
}).sort(function(a, b) {
return a.a[0].num - b.a[0].num
}).reverse();
console.log(result)
.as-console-wrapper {
max-height: 100% !important;
}
Map and Reduce
const data = [{ a: [{ num: 31 }, { num: 10 }] }, { a: [{ num: 4 }, { num: 9 }] }, { a: [{ num: 5 }, { num: 9 }] }];
data.map((value) => {
value.a = [
{
num: value.a.reduce((accumulatedValue, currentValue) => {
return Math.max(accumulatedValue.num, currentValue.num);
}),
},
];
return value;
});
console.log(data)

Javascript: How would you get the highest rated product category based on these 2 arrays

Assuming I have 2 arrays:
const products = [
{
name: 'prod1',
category: 'Meat'
},
{
name: 'prod2',
category: 'Meat'
},
{
name: 'prod3',
category: 'Dairy'
}];
const rate = [
{
name: 'prod1',
rate: 23,
},
{
name: 'prod2',
rate: 36
},
{
name: 'prod3',
rate: 50,
}];
How would you get the category that has the highest sum rate? For example, prod1 and prod2 share the same category 'Meat" and hence the rate for meat is 36 + 23 = 59.
The way I thought about it is to create an adjusted Array of products where each entry will contain the rate from the second array and then I will create a result array and push an object of category and sumRate after iterating the adjustedArray.
So if the result Array has an object with category, I would adjust the sum and add the new rate, if not I'll create a new entry with category: rate.
Can we do this in a very optimal way?
As the OP probably knows, canonical grouping goes like this...
const prodsByCategory = products.reduce((acc, p) => {
let cat = p.category;
if (!acc[cat]) acc[cat] = [];
acc[cat].push(p);
return acc;
}, {});
Modify this a little to add the data which will be needed to optimize.
const prodsByCategory = products.reduce((acc, p) => {
let cat = p.category;
// instead of just an array, keep an array and a total
if (!acc[cat]) acc[cat] = { products: [], totalRate: 0 };
// instead of just pushing, push and increment total with a lookup
acc[cat].products.push(p);
acc[cat].totalRate += rateForProduct(p) || 0;
return acc;
}, {});
We need a lookup for rateForProduct, like this:
const rateForProduct = product => {
return rate.find(r => r.name === product.name)?.rate || 0;
}
That should produce an object keyed by category with values that have a prop called totalRate. Sort those entries so that the first one is maximized. Here's a demo...
const products = [{
name: 'prod1',
category: 'Meat'
},
{
name: 'prod2',
category: 'Meat'
},
{
name: 'prod3',
category: 'Dairy'
}
];
const rate = [{
name: 'prod1',
rate: 23,
},
{
name: 'prod2',
rate: 36
},
{
name: 'prod3',
rate: 50,
}
];
const rateForProduct = product => {
return rate.find(r => r.name === product.name)?.rate || 0;
}
const prodsByCategory = products.reduce((acc, p) => {
let cat = p.category;
if (!acc[cat]) acc[cat] = {
products: [],
totalRate: 0
};
acc[cat].products.push(p);
acc[cat].totalRate += rateForProduct(p);
return acc;
}, {});
const sortedEntries = Object.entries(prodsByCategory).sort((a, b) => b[1].totalRate - a[1].totalRate);
const bestEntry = {
category: sortedEntries[0][0],
rate: sortedEntries[0][1].totalRate
}
console.log(bestEntry);

how to assign object in object

how to assign the object in object and filter the value which pass and fail;
the input is:
[
{
name: 'John',
score: 90,
time: 'evening'
},
{
name: 'Doni',
score: 68,
time: 'morning'
},
{
name: 'Jiu',
score: 50,
time: 'evening'
},
{
name: 'Shin',
score: 92,
time: 'morning'
},
];
and i want the output like this :
{
"evening": {
"pass": [
{
"name": "John",
"score": 90
}
],
"fail": [
{
"name": "jiu",
"score": 50
}
]
},
"morning": {
"pass": [
{
"name": "Shin",
"score": 92
}
],
"fail": [
{
"name": "Doni",
"score": 68
}
]
}
}
do we need to use Object.assign for this ? and how many loop we use for this ??
i do love to know how to add another string in the object beside that ouput,
thanks
There's a lot of ways to do this. The simplest is probably to make a base object that represent your empty results. Then loop over the students and fill the arrays:
let students = [{name: 'John',score: 90,time: 'evening'},{name: 'Doni',score: 68,time: 'morning'},{name: 'Jiu',score: 50,time: 'evening'},{name: 'Shin',score: 92,time: 'morning'},];
// Empty case
let base = {
"evening": {"pass": [], "fail": []},
"morning": {"pass": [], "fail": []}
}
const PASSING = 70
students.forEach(({name, score, time}) => {
let key = score >= PASSING ? 'pass' : 'fail'
base[time][key].push({name, score})
})
console.log(base)
This makes is easy to have empty arrays, which is probably what you want if there are no students in a particular category.
EDIT based on comment:
To support arbitrary times, you can just create the times on the object as you find them. reduce() is good for this, but you could also use a regular loop. For example with an added afternoon time:
let students = [{name: 'Mark',score: 95,time: 'afternoon'}, {name: 'John',score: 90,time: 'evening'},{name: 'Doni',score: 68,time: 'morning'},{name: 'Jiu',score: 50,time: 'evening'},{name: 'Shin',score: 92,time: 'morning'},];
const PASSING = 70
let result = students.reduce((obj, {name, score, time}) => {
if (!obj[time]) obj[time] = {'pass': [], 'fail': [] }
let key = score >= PASSING ? 'pass' : 'fail'
obj[time][key].push({name, score})
return obj
}, {})
console.log(result)
You can do something like this:
const data = [{ name: 'John', score: 90, time: 'evening' }, { name: 'Doni', score: 68, time: 'morning' }, { name: 'Jiu', score: 50, time: 'evening' }, { name: 'Shin', score: 92, time: 'morning' }, ];
const grp = (d, p) => d.reduce((r,c) => (r[c[p]] = [...r[c[p]] || [], c], r), {})
const grpV = (d, rng) => d.reduce((r,{name, score}) => {
let key = score > rng ? 'pass' : 'fail'
r[key] = [...r[key] || [], {name, score}]
return r
}, {})
const r = Object.entries(grp(data, 'time')).map(([k,v]) => ({[k]: grpV(v, 75)}))
console.log(r)
The idea is the group 2 times one on the time and 2nd on the score.
grp: function to group by a property (in this case 'time') which returns an object with 2 properties: evening and morning each of which is an array containing the classes.
grpV: function to group by value (in this case 75) which returns an object with 2 properties: pass and fail each of which is an array containing the classes.
On the end once we have those tools we are saying ... give me the entries of the grouped by time object and for each of the groups ... group by score.
Here how something like this could look like if we ware using lodash:
const data = [{ name: 'John', score: 90, time: 'evening' }, { name: 'Doni', score: 68, time: 'morning' }, { name: 'Jiu', score: 50, time: 'evening' }, { name: 'Shin', score: 92, time: 'morning' }, ];
const partition = (x, p) => _(x)
.partition(y => y.score > p)
.map((x,i) => ({ [i==0 ? 'pass': 'fail']: _.omit(x[0], 'time')}))
.value()
const r = _(data)
.groupBy('time')
.mapValues(x => partition(x, 75))
.value()
console.log(r)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Adding it as an example since it does help with readability of what the ES6 example is doing to some extend.
I'm sure there are more elegant ways to do this. But this one is probably one of the simplest beginner-friendly ways you can go about this.
I loop through the input array, check the existence of the .time values as keys on the output object and create the pass and fail keys. Then evaluate the .score against the passingScore and push the necessary data to it.
Should be pretty easy to understand once you see and try the code below:
const data = [
{name: 'John',score: 90, time: 'evening'},
{name: 'Doni',score: 68, time: 'morning'},
{name: 'Jiu',score: 50, time: 'evening'},
{name: 'Shin',score: 92, time: 'morning'},
{name: 'Fubar',score: 75, time: 'noon'},
];
function formatData(data){
const passingScore = 75;
const output = {};
data.forEach(function(item){
if(!output[item.time]) output[item.time] = {pass: [], fail: []};
const stud = { name: item.name, score: item.score };
if(item.score >= passingScore) output[item.time]['pass'].push(stud)
else output[item.time]['fail'].push(stud)
});
return output;
}
console.log(formatData(data));

Group multiple elements in array with JavaScript

I have an array
[
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
and I want it transformed into
[
{ numElements: 4, price: 10 },
{ numElements: 2, price: 20 },
]
I have tried using arr.reduce((prev, curr) => ..., []) to accomplish this, but I can't figure out how to do it.
A traditional method might use a for/loop to wrangle the data, but these days JavaScript has a number of functional methods that can help. This code uses reduce and map. To get your data in the format you want is a two stage process.
First, use reduce to create a hash table using the price as a key (because you know the each price is going to be unique:
const obj = arr.reduce((p, c) => {
// If price exists as a key its value by 1
// otherwise set it to 1.
p[c.price] = ++p[c.price] || 1;
return p;
}, {});
OUTPUT
{
"10": 4,
"20": 2
}
As it stands you've got a perfectly good object that you can access by the key/price and I would probably just stop there:
obj['10'] // 4
But if you want to get that data into the format in your question, map over the object keys to return an array of new objects.
const out = Object.keys(obj).map(key => {
return { price: +key, numElements: obj[key] };
});
DEMO
var hash = {}, result = [];
arr.forEach(function(el){
if(hash[el.price]){
hash[el.price].numElements++;
}else{
result.push(hash[el.price]={price:el.price,numElements:1});
}
});
Run
May use a hash table for price lookup. Or with reduce and find:
arr.reduce((res,{price})=>
(( res.find(el=>el.price===price) || res[res.push({price,numElements:0})-1] )
.numElements++,res)
);
Run
You can use try this:
let arr = [
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 10 },
{ price: 20 },
{ price: 20 },
]
let result = []
let counter = {}
arr.forEach( el => {
if (!counter[el.price]) counter[el.price] = 1
else counter[el.price]++
console.log(counter[el.price])
})
for (let id in counter) {
result.push({numElements: counter[id], price: id})
}
Assuming that the data comes sorted on price property, with a single .reduce() you may do as follows;
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
result = data.reduce((r,d,i) => i ? r[r.length-1].price === d.price ? (r[r.length-1].numElemenets++, r)
: (r.push(Object.assign({}, d, {numElemenets: 1})),r)
: [Object.assign({}, d, {numElemenets: 1})], {});
console.log(result);
You could look up the price in the result array and if not found insert a new object.
var data = [{ price: 10 }, { price: 10 }, { price: 10 }, { price: 10 }, { price: 20 }, { price: 20 }],
grouped = data.reduce((r, { price }) => {
var t = r.find(p => price === p.price);
t || r.push(t = { numElements: 0, price });
t.numElements++;
return r;
}, []);
console.log(grouped);

How to sort a JavaScript array by more nested objects in arrays and grab the top ###?

Here is a dummy example. I have an array of objects:
var cars = [
{
name: "Hyundai",
plans: [
{
name: "Something",
add-ons: [
{
cost: 100
},
{
cost: 75
}
]
}, { ... }
]
},
{
name: "Jeep",
plans: [
{
name: "Something",
add-ons: [
{
cost: 50
},
{
cost: 75
}
]
}, { ... }
]
},
{
name: "Buick",
plans: [
{
name: "Something",
add-ons: [
{
cost: 35
},
{
cost: 50
}
]
}, {...}
]
}
]
What I'm trying to do is find the top 2 cars that have the cheapest add-on and reference them via another variable.
Like this:
var top2 = findTopTwo(cars);
findTopTwo(arr) {
return arr.sort(function(a, b) {
// My trouble spot
}).slice(0, 2);
}
With my simple example, the result for top2 would be:
Buick ( cheapest add-on was $35, the value used to compare against )
Jeep ( cheapest add-on was $50, value used to compare against )
So what I would do is feed all of them into an array and then sort it on the cost. That would be my naive approach. The more optimal solution would be to only store 2 objects at a given time instead of a list of all items.
The naive approach would be as simple as:
var items = [];
for ( var i in cars ){
var car = cars[i];
for (var i in car["plans"]){
for (var j = 0; j < car["plans"][i]["add-ons"]){
items.push({"name": car.name, "cost": car["plans"][i]["add-ons"][j]["cost"]});
}
}
}
return items.sort(function(a,b){ return a.cost < b.cost }).slice(0,2);
That will return a list of 2 objects, the object contains the name of the car and the cost. The more effecient thing would be to do something like this:
var biggest = function(arr){
if (arr.length < 2 ) return -1;
return arr[0].cost > arr[1].cost ? 0 : 1;
}
var items = [];
for ( var i in cars ){
var car = cars[i];
for (var i in car["plans"]){
for (var j = 0; j < car["plans"][i]["add-ons"]){
var obj = {"name": car.name, "cost": car["plans"][i]["add-ons"][j]["cost"]};
}
var index = biggest(items)
if (index < 0){
items.push(obj);
}else{
if (items[index].cost > obj.cost)
items[index] = obj;
}
}
}
return items;
this more interesting design will push the first 2 into the list, but then it will find the biggest of the 2 costs and then checks to see if the new one is smaller than it. If the new one is smaller than item[index] it will be replaced.
This will never have the array larger than 2 so it takes up less memory
Another approach. By this approach your original data will not be sorted or modified.
var cars=[{name:"Hyundai",plans:[{name:"Something","add-ons":[{cost:100},{cost:75}]}]},
{name:"Jeep",plans:[{name:"Something","add-ons":[{cost:50},{cost:75}]}]},
{name:"Buick",plans:[{name:"Something","add-ons":[{cost:35},{cost:50}]}]}];
function findTopTwo(cars) {
return cars.map(
car =>
car.plans.reduce(
(prevPlan, plan) =>
plan['add-ons'].reduce((prevAddOn, addOn) => {
if (prevAddOn.cost > addOn.cost) {
prevAddOn.cost = addOn.cost;
}
return prevAddOn;
}, prevPlan), {
cost: Number.MAX_VALUE,
name: car.name
})
)
.sort((a, b) => a.cost - b.cost)
.slice(0, 2)
.map(item => item.name);
}
console.log(findTopTwo(cars));
I had to play around with the object, but here is the gist of it -
var cars = [{
name: "Hyundai",
plans: {
addons: [{
cost: 100
}, {
cost: 75
}]
}
}, {
name: "Jeep",
plans: {
addons: [{
cost: 50
}, {
cost: 75
}]
}
}, {
name: "Buick",
plans: {
addons: [{
cost: 35
}, {
cost: 50
}]
}
}];
var top2 = findTopTwo(cars);
console.log(top2);
function findTopTwo(arr) {
return arr.sort(function (a, b) {
// this map outputs array of costs: [35, 40]
// and Math.min takes the lowest value of each
var a_max_cost = Math.min.apply(null, a.plans.addons.map(function(i){i.cost})),
b_max_cost = Math.min.apply(null, b.plans.addons.map(function(i){i.cost}));
return a_max_cost - b_max_cost;
})
.slice(0, 2);
}
Basically, you need to return a-b in the sort function, where a and b are the lowest addon values. So I calculated the max of both cars on comparison, and used those values to decide which goes where.
Edit: I see you've updated the JS object, the answer should be similar to min, you will only need to figure out which plan to use for a and b. You can do so similar to my use of the Math.max function
One simple way of doing it is to first sort the addons by price (if you don't mind the side effect that addons then remain sorted by price).
function findTopTwo(arr) {
arr.forEach(function (elem) {
elem.plans.addons = elem.plans.addons.sort(function (a, b) {
return a.cost > b.cost;
});
});
return arr.sort(function(a, b) {
return a.plans.addons[0].cost > b.plans.addons[0].cost;
}).slice(0, 2);
}
jsbin example
Using #casraf's data:
const sortedCars = cars.map(car => {
car.plans.addons.sort((a, b) => a.cost - b.cost);
return car;
}).sort((a, b) => {
return a.plans.addons[0].cost - b.plans.addons[0].cost;
});
Line 2 sorts each cars' addons array from low to high. Line 5 sorts the cars from low to high based on the first index of their respective addons property.
If the ES6 syntax is confusing, here's a translation to ES5
I suggest to use sorting with map, then take the top 2 entries and get the data from cars.
var cars = [{ name: "Hyundai", plans: [{ 'add-ons': [{ cost: 100 }, { cost: 75 }] }] }, { name: "Jeep", plans: [{ 'add-ons': [{ cost: 50 }, { cost: 75 }] }] }, { name: "Buick", plans: [{ 'add-ons': [{ cost: 35 }, { cost: 50 }] }] }],
cost = cars.
map(function (a, i) {
return {
index: i,
cost: a.plans.reduce(function (r, b) {
return Math.min(r, b['add-ons'].reduce(function (s, c) {
return Math.min(s, c.cost);
}, Infinity));
}, Infinity)
};
}).
sort(function (a, b) { return a.cost - b.cost; }),
top2 = cost.slice(0, 2).map(function (a) {
return cars[a.index];
});
console.log(top2);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories