Sorting objects by property values - javascript

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

Related

sort array by 2 dynamic keys

I have an array like this. I need to write a function to pass in any two fields and have it sorted. example sort('company_id', 'title') should first sort by company_id and then title.
How to write a generic function for it
This is the array and my logic:
[ {
id: 11861,
deadline: '01/13/2020',
status: 'printed',
buyer_name: 'Dion Murray PhD'
},
{
id: 11848,
deadline: '12/14/2019',
status: 'published',
buyer_name: 'Dion Murray PhD'
},
{
id: 11849,
deadline: '12/14/2019',
status: 'published',
buyer_name: 'Dion Murray PhD'
},
{
id: 11857,
deadline: '12/22/2019',
status: 'new',
buyer_name: 'Dion Murray PhD'
}
]
sort ( dataToSort, sortField1 , sortField2) {
rawData.dataToSort( (a, b) => (a[sortField1] > b[sortField1]) ? 1 : ((a[sortField2] > b[sortField2) ? 1 : -1)} ```
TIA.
You could take a comparison which works for numbers as well as for strings
a > b || -(a < b)
and get the wanted properties and chain the conditions.
sortBy = (key1, key2) => (a, b) =>
a[key1] > b[key1] || -(a[key1] < b[key1]) || a[key2] > b[key2] || -(a[key2] < b[key2])
usage:
array.sort(sortBy('company_id', 'title'));
for using an arbitrary count of keys, you could take the parameters as array and iterate the array until the callback gets a value not equal zero.
const
sortBy = (...keys) => (a, b) => {
var r = 0;
keys.some(k => r = a[k] > b[k] || -(a[k] < b[k]));
return r;
};
usage:
array.sort(sortBy('company_id', 'title', 'status'));
You can also use the module lodash with sortBy
Example :
const _ = require('lodash');
const users = [
{ 'user': 'fred', 'age': 48 },
{ 'user': 'barney', 'age': 36 },
{ 'user': 'fred', 'age': 40 },
{ 'user': 'barney', 'age': 34 }
];
_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
// generic sort, 'propa, propb desc'
function sort(data, sort) {
var asort=sort.split(',');
var psort=[];
for(var i=0,l=asort.length;i<l;i++) {
var sprop=asort[i].trim();
if (!sprop) continue;
var spropa=sprop.split(' ');
psort.push({prop:spropa[0],desc:(spropa[1]=='desc')?-1:1});
}
var psortl=psort.length;
data.sort(function(a,b) {
for(var i=0;i<psortl;i++) {
var cpsort=psort[i];
if (a[cpsort.prop]>b[cpsort.prop]) return cpsort.desc;
if (a[cpsort.prop]<b[cpsort.prop]) return 0-cpsort.desc;
}
return 0;
});
return data;
}
You can try :
obj.sort(function(a, b) {
return a["company_id"] - b["company_id"] || a["title"] - b["title"];
});

Chaining multiple filters if condition exist

I'm trying to chain two filters, based in two ranges (arrays) of params that may also be empty, so it would be possible that f.ex. speedlimit=[]
var speedfilter =[240,300]
var pricefilter = [80,120]
var cars = [
{name:'Ferrari', maxspeed:240, price: 100},
{name:'Porsche', maxspeed:220, price: 90},
{name:'Bugatti', maxspeed:300, price: 500}
];
if (speedfilters) {
return cars.filter(function (car) {
return car.maxspeed >= speedfilter[0] && car.maxspeed <= speedfilter[1];
})
} else if (pricefilter) {
return cars.filter(function (car) {
return car.price >= pricefilter[0] && car.price <= pricefilter[1];
})
}
else return cars
The result in the example above should output {name:'Ferrari', speed:240, price: 100}
What would be the way to do it with javascript filter? Thanks in advance!
You could create a filterCar method via prototype inheritance
Array.prototype.filterCar = function(feature, range) {
return this.filter((el) => {
// is the range defined?
if (!!range.length) {
return el[feature] >= range[0] && el[feature] <= range[1];
}
else {
return true;
}
})
};
var cars = [
{name:'Ferrari', speed:240, price: 100},
{name:'Porsche', speed:220, price: 90},
{name:'Bugatti', speed:300, price: 500}
];
var result1 = cars.filterCar('speed', [240, 300])
.filterCar('price', [80, 120]));
var result2 = cars.filterCar('speed', [ ])
.filterCar('price', [80, 120]));
console.log(result1); // [{name: "Ferrari", speed: 240, price: 100}]
console.log(result2); /* [{name: "Ferrari", speed: 240, price: 100},
{name: "Porsche", speed: 220, price: 90}] */
You can wrap your filtering up into a re-usable method, and this can account for the filter not being available.
function filterCars(carsArray, property, rangeArray) {
// if rangeArray is not supplied, or is empty, just return the unfiltered input
if(!rangeArray|| rangeArray.length === 0) {
return carsArray;
}
// otherwise filter according to logic
return carsArray.filter(car => car[property] >= rangeArray[0] && car[property] <= rangeArray[1]);
}
This can be chained, or for more readability called in sequence:
function filterCars(carsArray, property, rangeArray) {
if(!rangeArray|| rangeArray.length === 0) {
return carsArray;
}
return carsArray.filter(car => car[property] >= rangeArray[0] && car[property] <= rangeArray[1]);
}
var speedfilter = []; // [240,300]
var pricefilter = [80,120]
var cars = [
{name:'Ferrari', maxspeed:240, price: 100},
{name:'Porsche', maxspeed:220, price: 90},
{name:'Bugatti', maxspeed:300, price: 500}
];
cars = filterCars(cars,"maxspeed",speedfilter);
cars = filterCars(cars,"price",pricefilter);
console.log(cars);

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

How to get Property value from a Javascript object

I have a JavaScript object.
var obj = { Id: "100", Name: "John", Address: {Id:1,Name:"Bangalore"} }
var dataToRetrieve= "Name";
function GetPropertyValue(object,dataToRetrieve){
return obj[dataToRetrieve]
}
var retval = GetPropertyValue(obj,dataToRetrieve)
This works fine. But if I try to get the value of property value of "Address.Name" ,
Like : var dataToRetrieve = "Address.Name";
it shows undefined.
Note : The property variable is set by user from HTML And it can be changed according to user requirement(which property value he wants).
What I want to achieve :
1) If dataToRetrieve = "Name" , it should give me "John",
2) If dataToRetrieve = "Id" , it should give me "100",
3) If dataToRetrieve = "Address.Name" , it should give me "Bangalore",
4) If dataToRetrieve = "Address.Id" , it should give me 1
Plunkr Here : PLUNKR
Use reduce() method
var obj = {
Id: "100",
Name: "John",
Address: {
Id: 1,
Name: "Bangalore"
}
}
function GetPropertyValue(obj1, dataToRetrieve) {
return dataToRetrieve
.split('.') // split string based on `.`
.reduce(function(o, k) {
return o && o[k]; // get inner property if `o` is defined else get `o` and return
}, obj1) // set initial value as object
}
console.log(
GetPropertyValue(obj, "Name"),
GetPropertyValue(obj, "Id"),
GetPropertyValue(obj, "Address.Name"),
GetPropertyValue(obj, "Address.Id"),
GetPropertyValue(obj, "Address.Idsd"),
GetPropertyValue(obj, "Addre.Idsd")
)
For older browser check polyfill option of reduce method.
Use following function:
var obj = { Id: "100", Name: "John",
Address: [{ Id:1, Name:"Bangalore" }, { Id:2, Name: "Mysore" } ] };
function GetPropertyValue(object, dataToRetrieve) {
dataToRetrieve.split('.').forEach(function(token) {
if (object) object = object[token];
});
return object;
}
console.log(
GetPropertyValue(obj, "Address.0.Name"),
GetPropertyValue(obj, "Address.1.Id"),
GetPropertyValue(obj, "Name"),
GetPropertyValue(obj, "Id"),
GetPropertyValue(obj, "Unknown"),
GetPropertyValue(obj, "Some.Unknown.Property")
);
function GetPropertyValue(object,dataToRetrieve){
var valueArray = dataToRetrieve.split(".");
if (valueArray.length <= 1) {
return object[valueArray];
} else {
var res;
function browseObj(obj, valueArray, i) {
if (i == valueArray.length)
res = obj;
else
browseObj(obj[valueArray[i]], valueArray, i+1);
}
browseObj(object, valueArray, 0);
return res;
}
}
I had written a standard reusable Object method to access nested properties dynamically. It's like
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
It will take dynamic arguments for the nested properties. If they are string type they are object properties if number type then they are array indices. Once you have this, your job becomes very easy. Let's see..
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
var props = ["Address","Name"],
obj = { Id: "100", Name: "John", Address: {Id:1,Name:"Bangalore"} },
val = obj.getNestedValue(...props);
console.log(val);
// or you can of course do statically like
val = obj.getNestedValue("Address","Name");
console.log(val);
You can see getNestedValue() and it's twin setNestedValue() working at https://stackoverflow.com/a/37331868/4543207

Grouped sorting on a JS array

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

Categories