Grouped sorting on a JS array - javascript

I have an array of objects with two properties: Name and Hours.
For example:
array = [
{name: "ANDY", hours: 40 },
{name: "ANDY", hours: 50 },
{name: "GREG", hours: 40 },
]
For example in my array I would like the result of the sorting to have the Andy with the most hours first, then Andy with slightly less hours, and then Greg because his name comes later alphabetically and so on and so on.
Since the array.sort() function passes two elements of the array to compare i realise this is not the way to go for me but fail to come up with an elegant solution. Please help me out.

array = [
{name: "ANDY", hours: 40 },
{name: "GREG", hours: 40 },
{name: "ANDY", hours: 50 },
]
function cmp(x, y) {
return x > y ? 1 : (x < y ? -1 : 0);
}
array.sort(function(a, b) {
return cmp(a.name, b.name) || cmp(b.hours, a.hours)
})
console.log(array)
If javascript had a spaceship operator that would be even more elegant. Note that this code is easy to extend to use more properties:
ary.sort(function(a, b) {
return cmp(a.name, b.name) || cmp(a.age, b.age) || cmp(b.hours, a.hours) || ....
})

var arr = [{
name: "GREG",
hours: "40"
}, {
name: "ANDY",
hours: "50"
}, {
name: "ANDY",
hours: "40"
}];
Array.prototype.sortOn = function(conds){
this.sort(function(a, b){
for(var i=0; i < conds.length; i++){
var c = conds[i].split(" ");
if(a[c[0]] < b[c[0]]){
return c[1] == "asc" ? -1:1;
}
if(a[c[0]] > b[c[0]]){
return c[1] == "asc" ? 1:-1;
}
}
return 0;
});
return this;
}
arr.sortOn(["name asc", "hours dsc"]);

obj.sort(function(item1,item2) {
if ( item1.Name < item2.Name )
return -1;
if ( item1.Name > item2.Name )
return 1;
return item1.Hours - item2.Hours;
});

You can sort by Name, then sort elements who have the same name by Hours
Example:
var array = [{"Name":"ANDY", "Hours":40},
{"Name":"ANDY", "Hours":50},
{"Name":"GREG", "Hours":40}];
var sortedArray = array.sort(function(a,b) {
return (a["Name"] > b["Name"]) ? 1 : -1;
}).sort(function(a,b) {
if(a["Name"] == b["Name"])
return (a["Hours"] < b["Hours"]) ? 1 : -1;
else
return 0;
});

Related

Javascript : Add Key & Value at specific index in array of Object

I have array of Object like this.
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}]
let newVal = arr.map(function(el) {
if(el.age > 25){
var o = Object.assign({}, el);
o.gender = 'male';
return o;
}
})
console.log("New Val : " , newVal)
I would like to add {gender:'male'} to object where age is > 25
It says undefined to other objects.
Any help would be great.
Thank You.
You need to return the value if the object doesn't match the condition. Since you haven't retrned anything from from inside map if the condition is not fulfilled, you get undefined for the other objects
let arr = [{
name: "abc",
age: 26
}, {
name: "xyz",
age: 23
}, {
name: "pqr",
age: 10
}]
let newVal = arr.map(function(el) {
if (el.age > 25) {
var o = Object.assign({}, el);
o.gender = 'male';
return o;
}
return el; // return value here
})
console.log("New Val : ", newVal)
issue with your code is already solved in other answer by Shubham, ie when if clause is not executed you are not returning anything.
but i think forEach might be cleaner here
if you want to keep the original array you can copy it using copyArr = [...arr]
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}]
arr.forEach(function(el) {
if(el.age > 25)
el.gender = 'male';
})
console.log("New Val : " , arr)
It's missing the return statement when the condition is false.
You can do this in one line using an arrow function as follow:
let arr = [{name:"abc",age:26},{name:"xyz",age:23},{name:"pqr",age:10}],
newVal = arr.map((el) => Object.assign({}, el, el.age > 25 ? {gender: "male"} : {}));
console.log("New Val:", newVal);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You get a new array with map. Inside, you need to take either an copy of the object with a new property or the original object.
let array = [{ name: "abc", age: 26 }, { name: "xyz", age: 23 }, { name: "pqr", age: 10 }],
result = array.map(object => object.age > 25
? { ... object, gender: 'male' }
: object
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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; }

reduce more than one value

Is there any way that I can return more than one object with the reduce function? For example, I want to return the oldest age and I have 3 people who are all the same age. See below, thanks!
var dataArray = [
{name: "Roy", age: 24, sex: "M"},
{name: "Ben", age: 25, sex: "M"},
{name: "Jamie", age: 23, sex: "F"},
{name: "David", age: 25, sex: "M"},
{name: "Bob", age: 25, sex: "M"}
];
var oldestPeople = dataArray.reduce(function(max, cur) {
console.log("Max is " + max["name"]);
console.log("Cur is " + cur["name"]);
if (cur["age"] === max["age"]) {
return [max, cur];
} else if (cur["age"] > max["age"]) {
return cur;
} else {
return max;
}
});
I am able to get it to return two objects, but my max becomes undefined once [max, cur] is returned. Thanks again!
Looking at the documentation https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
then the first callback it the return value from the previous invocation of the callback.
So the problem you are having is that sometimes you return and array [max,cut] and other times you return a single value cur or max -- just stick to always return an array and you should be fine, like
if (max)
console.log("Max is " + max[0]["name"]);
else
return [cur];
console.log("Cur is " + cur["name"]);
if (cur["age"] === max[0]["age"]) {
return max.concat([cur]);
} else if (cur["age"] > max[0]["age"]) {
return [cur].concat(max);
} else {
return max.concat([cur]);
}
If you want to use reduce, you could use something like
var oldestPeople = dataArray.reduce(function(max, cur) {
if (cur.age === max.age) {
max.people.push(cur);
} else if (cur.age > max.age) {
max.age = cur.age;
max.people = [cur];
}
return max;
}, {age: -Infinity, people: []}).people;
But maybe I would prefer a forEach:
var maxAge = -Infinity,
oldestPeople = [];
dataArray.forEach(function(cur) {
if (cur.age === maxAge) {
oldestPeople.push(cur);
} else if (cur.age > maxAge) {
maxAge = cur.age;
oldestPeople = [cur];
}
});

Sort an array of objects based on values in the objects [duplicate]

This question already has answers here:
Sort array of objects by string property value
(57 answers)
Closed 8 years ago.
I have an array of objects
var winners_tie = [
{name: 'A', value: 111},
{name: 'B', value: 333},
{name: 'C', value: 222},
]
I wanna sort it in ascending order of value
Since your values are just numbers, you can return their differences from the comparator function
winners_tie.sort(function(first, second) {
return first.value - second.value;
});
console.log(winners_tie);
Output
[ { name: 'A', value: 111 },
{ name: 'C', value: 222 },
{ name: 'B', value: 333 } ]
Note: JavaScript's sort is not guaranteed to be stable.
Try this one:
function compare(a,b) {
if (a.value < b.value)
return -1;
if (a.value > b.value)
return 1;
return 0;
}
winners_tie.sort(compare);
For Demo : Js Fiddle
For arrays:
function sort_array(arr,row,direc) {
var output = [];
var min = 0;
while(arr.length > 1) {
min = arr[0];
arr.forEach(function (entry) {
if(direc == "ASC") {
if(entry[row] < min[row]) {
min = entry;
}
} else if(direc == "DESC") {
if(entry[row] > min[row]) {
min = entry;
}
}
})
output.push(min);
arr.splice(arr.indexOf(min),1);
}
output.push(arr[0]);
return output;
}
http://jsfiddle.net/c5wRS/1/

Sorting objects by property values

How to implement the following scenario using Javascript only:
Create a car object with properties (top speed, brand, etc.)
Sort a list of cars ordered by those properties
javascript has the sort function which can take another function as parameter - that second function is used to compare two elements.
Example:
cars = [
{
name: "Honda",
speed: 80
},
{
name: "BMW",
speed: 180
},
{
name: "Trabi",
speed: 40
},
{
name: "Ferrari",
speed: 200
}
]
cars.sort(function(a, b) {
return a.speed - b.speed;
})
for(var i in cars)
document.writeln(cars[i].name) // Trabi Honda BMW Ferrari
ok, from your comment i see that you're using the word 'sort' in a wrong sense. In programming "sort" means "put things in a certain order", not "arrange things in groups". The latter is much simpler - this is just how you "sort" things in the real world
make two empty arrays ("boxes")
for each object in your list, check if it matches the criteria
if yes, put it in the first "box"
if no, put it in the second "box"
Example.
This runs on cscript.exe, on windows.
// define the Car class
(function() {
// makeClass - By John Resig (MIT Licensed)
// Allows either new User() or User() to be employed for construction.
function makeClass(){
return function(args){
if ( this instanceof arguments.callee ) {
if ( typeof this.init == "function" )
this.init.apply( this, (args && args.callee) ? args : arguments );
} else
return new arguments.callee( arguments );
};
}
Car = makeClass();
Car.prototype.init = function(make, model, price, topSpeed, weight) {
this.make = make;
this.model = model;
this.price = price;
this.weight = weight;
this.topSpeed = topSpeed;
};
})();
// create a list of cars
var autos = [
new Car("Chevy", "Corvair", 1800, 88, 2900),
new Car("Buick", "LeSabre", 31000, 138, 3700),
new Car("Toyota", "Prius", 24000, 103, 3200),
new Car("Porsche", "911", 92000, 155, 3100),
new Car("Mercedes", "E500", 67000, 145, 3800),
new Car("VW", "Passat", 31000, 135, 3700)
];
// a list of sorting functions
var sorters = {
byWeight : function(a,b) {
return (a.weight - b.weight);
},
bySpeed : function(a,b) {
return (a.topSpeed - b.topSpeed);
},
byPrice : function(a,b) {
return (a.price - b.price);
},
byModelName : function(a,b) {
return ((a.model < b.model) ? -1 : ((a.model > b.model) ? 1 : 0));
},
byMake : function(a,b) {
return ((a.make < b.make) ? -1 : ((a.make > b.make) ? 1 : 0));
}
};
function say(s) {WScript.Echo(s);}
function show(title)
{
say ("sorted by: "+title);
for (var i=0; i < autos.length; i++) {
say(" " + autos[i].model);
}
say(" ");
}
autos.sort(sorters.byWeight);
show("Weight");
autos.sort(sorters.byModelName);
show("Name");
autos.sort(sorters.byPrice);
show("Price");
You can also make a general sorter.
var byProperty = function(prop) {
return function(a,b) {
if (typeof a[prop] == "number") {
return (a[prop] - b[prop]);
} else {
return ((a[prop] < b[prop]) ? -1 : ((a[prop] > b[prop]) ? 1 : 0));
}
};
};
autos.sort(byProperty("topSpeed"));
show("Top Speed");
I have wrote this simple function for myself:
function sortObj(list, key) {
function compare(a, b) {
a = a[key];
b = b[key];
var type = (typeof(a) === 'string' ||
typeof(b) === 'string') ? 'string' : 'number';
var result;
if (type === 'string') result = a.localeCompare(b);
else result = a - b;
return result;
}
return list.sort(compare);
}
for example you have list of cars:
var cars= [{brand: 'audi', speed: 240}, {brand: 'fiat', speed: 190}];
var carsSortedByBrand = sortObj(cars, 'brand');
var carsSortedBySpeed = sortObj(cars, 'speed');
Here's a short example, that creates and array of objects, and sorts numerically or alphabetically:
// Create Objects Array
var arrayCarObjects = [
{brand: "Honda", topSpeed: 45},
{brand: "Ford", topSpeed: 6},
{brand: "Toyota", topSpeed: 240},
{brand: "Chevrolet", topSpeed: 120},
{brand: "Ferrari", topSpeed: 1000}
];
// Sort Objects Numerically
arrayCarObjects.sort((a, b) => (a.topSpeed - b.topSpeed));
// Sort Objects Alphabetically
arrayCarObjects.sort((a, b) => (a.brand > b.brand) ? 1 : -1);
Let us say we have to sort a list of objects in ascending order based on a particular property, in this example lets say we have to sort based on the "name" property, then below is the required code :
var list_Objects = [{"name"="Bob"},{"name"="Jay"},{"name"="Abhi"}];
Console.log(list_Objects); //[{"name"="Bob"},{"name"="Jay"},{"name"="Abhi"}]
list_Objects.sort(function(a,b){
return a["name"].localeCompare(b["name"]);
});
Console.log(list_Objects); //[{"name"="Abhi"},{"name"="Bob"},{"name"="Jay"}]
With ES6 arrow functions it will be like this:
//Let's say we have these cars
let cars = [ { brand: 'Porsche', top_speed: 260 },
{ brand: 'Benz', top_speed: 110 },
{ brand: 'Fiat', top_speed: 90 },
{ brand: 'Aston Martin', top_speed: 70 } ]
Array.prototype.sort() can accept a comparator function (here I used arrow notation, but ordinary functions work the same):
let sortedByBrand = [...cars].sort((first, second) => first.brand > second.brand)
// [ { brand: 'Aston Martin', top_speed: 70 },
// { brand: 'Benz', top_speed: 110 },
// { brand: 'Fiat', top_speed: 90 },
// { brand: 'Porsche', top_speed: 260 } ]
The above approach copies the contents of cars array into a new one and sorts it alphabetically based on brand names. Similarly, you can pass a different function:
let sortedBySpeed =[...cars].sort((first, second) => first.top_speed > second.top_speed)
//[ { brand: 'Aston Martin', top_speed: 70 },
// { brand: 'Fiat', top_speed: 90 },
// { brand: 'Benz', top_speed: 110 },
// { brand: 'Porsche', top_speed: 260 } ]
If you don't mind mutating the orginal array cars.sort(comparatorFunction) will do the trick.
A version of Cheeso solution with reverse sorting, I also removed the ternary expressions for lack of clarity (but this is personal taste).
function(prop, reverse) {
return function(a, b) {
if (typeof a[prop] === 'number') {
return (a[prop] - b[prop]);
}
if (a[prop] < b[prop]) {
return reverse ? 1 : -1;
}
if (a[prop] > b[prop]) {
return reverse ? -1 : 1;
}
return 0;
};
};

Categories