sort array by 2 dynamic keys - javascript

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

Related

Javascript sort an array of objects by field and sorting direction

I have an array of objects like:
const arr = [
{
name: 'John',
age: 20,
},
{
name: 'Mark',
age: 30,
},
...
]
And I have a service invocation which has an order parameter which is an object with two properties: a field which is the field I want to sort my array of object by and an 'asc' which is a Boolean value for do I want the list in ascending or descending order.
const order = {
field: 'name',
asc: true,
}
I would have started something like this but it does not seem to be the solution
orderedList = list.sort((a, b) => {
if (order.asc) {
return a[order.field] - b[order.field];
} else {
return b[order.field] - a[order.field];
}
});
If you want to sort by string in alphabetical order, you can so something like this:
const arr = [{
name: 'John',
age: 20,
},
{
name: 'Mark',
age: 30,
},
{
name: 'Luke',
age: 19
}
]
const order = {
field: 'name',
asc: true,
}
orderedList = arr.sort((a, b) => {
if (order.asc) {
if (a[order.field] > b[order.field]) {
return 1
} else if (a[order.field] < b[order.field]) {
return -1
} else {
return 0
}
}
});
console.log(orderedList)
You can write the comparator function like so:
arr.sort((a, b) => {
var ret;
// assume ascending order and set return value to -1/0/1
if (a[order.field] < b[order.field]) {
ret = -1;
} else if (a[order.field] > b[order.field]) {
ret = 1;
} else {
ret = 0;
}
// for descending order simply invert the sign
return order.asc ? ret : -ret;
});
// note that arr is sorted in-place

How to convert array of objects into array with changed positions

I have an array of objects:
[
{ id: 'ade', value: 8 },
{ id: 'rtl', value: 17 },
{ id: 'enu', value: 11 }
]
and want to convert that to:
[
{ id: 'enu', value: 11 },
{ id: 'rtl', value: 17 },
{ id: 'ade', value: 8 },
]
but I can't reverse it etc, because I have to set a statement that item { id: 'ade', value: 8 } should be last item and rest must be sorted by id.
I tried to reduce first array and concat with the second one
const firstArr = arr.reduce((acc, cv) => {
if (cv.id === 'ade') {
acc.push(cv)
}
return acc
}, []))
const second = arr.splice(firstArr, 1).sort()
firstArr.concat(second)
but it failed and seems over-engineering. I'd like to do it smarter. Any ideas?
Please about any advice!!
You can use the spread operator to make the code look shorter,
But it's the same logic:
const arr = //...your array of data
const parsedArr = [...arr.filter(o => o.id !== 'ade').sort((a, b) => a.id.localeCompare(b.id)), ...arr.filter(o => o.id === 'ade')];
so the first part is all the array elements without the specific one that you need to be in the end, and the second part is the last element.
Hope I helped :)
You can filter, sort and then concatinate array with own rules:
let orig = [
{ id: 'ade', value: 8 },
{ id: 'rtl', value: 17 },
{ id: 'enu', value: 11 }
]
sorted = orig.filter(e => e.id !== 'ade').sort((a, b) =>
a.id.localeCompare(b.id)
).concat(orig.find(e => e.id === 'ade'))
console.log(sorted)
[
{ id: 'ade', value: 8 },
{ id: 'rtl', value: 17 },
{ id: 'enu', value: 11 },
].sort((a,b) => a.id == 'ade' ? 1 : b.id == 'ade' ? -1 : a.id > b.id ? 1 : -1)
You can provide custom logic in the callback passed to sort() to handle elements whose id === 'ade' differently than other elements.
If either id equals "ade" return the difference of booleans, else compare the two ids using localeCompare.
const arr = [
{ id: 'ade', value: 8 },
{ id: 'rtl', value: 17 },
{ id: 'enu', value: 11 }
];
const sortedArr = [...arr].sort((a, b) => (
(a.id === 'ade' || b.id === 'ade')
? (a.id === 'ade') - (b.id === 'ade')
: a.id.localeCompare(b.id)));
console.log(sortedArr)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Example – for a = 'ade' and b = 'rtl' the difference of the booleans returned by comparing them to ade returns 1 which sorts b before a and so pushes ade downward.
(('ade'==='ade') - ('rtl' === 'ade')) -> (true - false) -> (1 - 0) -> 1
You can useArray.prototype.localeCompare for your case
let arr = [
{ id: 'ade', value: 8 },
{ id: 'rtl', value: 17 },
{ id: 'enu', value: 11 }
]
arr.sort((a,b) => `${a.value}`.localeCompare(`${b.value}`))
console.log(arr)
For this particular case you can use custom sort function:
const sortedArr = [...arr].sort((a, b) => {
return b.id.localeCompare(a.id)
})
But if in the arr will be more objects you need to refactor sort function

Javascript: Remove duplicates in an array by compare function

I have an array with objects like this:
const array = [
{name:'obj1', address: 987, id: '123', location: 'zyx' },
{name:'obj2', address: 654, id: '456', location: 'wvu'},
{name:'obj3', address: 321, id: '123', location: 'zyx'}
];
and I want to remove the duplicates with a function to compare them:
const compareObjects = (a, b) => {
return a.id === b.id && a.location === b.location;
}
The function only compares the relevant properties of the objects.
How can I remove the duplicates from the array with this function?
Edit: To clarify, I want to use a function to compare some properties of the object and not the whole object.
You could reduce the array by checking the object of the temporary result set.
const
array = [{ name:'obj1', address: 987, id: '123', location: 'zyx' }, { name:'obj2', address: 654, id: '456', location: 'wvu' }, { name:'obj3', address: 321, id: '123', location: 'zyx' }],
compareObjects = (a, b) => a.id === b.id && a.location === b.location,
result = array.reduce((r, o) => {
if (!r.some(compareObjects.bind(null, o))) {
r.push(o);
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The obvious solution is to compare each element against each other element (except for itself):
const result = array.filter((el, i) => !arr.some((el2, i2) => i < i2 && compareObjects(el, el2));
However that is O(n²) which will get very slow for large datasets, in that case hashtables help you:
const dupes = new Set;
const key = el => el.id + "|" + el.location;
const result = array.filter(it => !dupes.has(key(el)) && dupes.add(key(el)));
That is O(n) (aka super fast, but consumes more memory).
You can try a function like below
function unique(array , compareObjects){
array.sort(compareObjects);
for(var i = 1; i < array.length; ){
if( compareObjects(array[i-1], array[i]) === 0){
array.splice(i, 1);
} else {
i++;
}
}
return array;
}

Sort a list by property and add an object before each first letter changes in JavaScript

So I am trying to make a UI like this:
And I have an array of users
[{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
What I am trying to do is to sort the array by first letter of the name property, and add a header object before each. For example in the picture, you can see the letter A, B, I, and J as the headers.
For now, I got it working like this:
let final = []
// sort by first letter
const sortedUsers = state.test_list.sort((a, b) => a.name.localeCompare(b.name))
for (let x = 0; x < sortedUsers.length; x++) {
const user = sortedUsers[x].name
if (user.charAt(0) === 'A') {
const checkIfExists = final.findIndex((f) => f.header === 'A')
// add the header A if it doesn't exist
if (checkIfExists < 0) final.push({header: 'A'})
}
else if (user.charAt(0) === 'B') {
const checkIfExists = final.findIndex((f) => f.header === 'B')
// add the header B if it doesn't exist
if (checkIfExists < 0) final.push({header: 'B'})
}
// else if up to the letter Z
final.push(user)
}
and if I log the final array, I get:
which is correct.
My concern is that the code is very long, and I have no idea if it can be optimized or make the code smaller.
Is there any other option to do something like this? Any help would be much appreciated.
Why don't you create a collection of names, which is grouped by the first letter? You can then loop on it, and create your list. Use Array#reduce to create the grouped collection.
And then use Object#keys to iterate over the grouped collection and render your results:
let data = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Alison'
}, {
name: 'Andrea'
}, {
name: 'Betty'
}];
let combined = data.reduce((result, item) => {
let letter = item.name[0].toUpperCase();
if (!result[letter]) {
result[letter] = [];
}
result[letter].push(item);
return result;
}, {});
console.log(combined);
// Iterate over the result
Object.keys(combined).forEach(key => {
// key will be the first letter of the user names and
// combined[key] will be an array of user objects
console.log(key, combined[key]);
});
One thing still to do is to sort the user arrays by user name, which you can do easily using Array#sort.
Simple enough, try sorting them and then using .reduce:
const unsortedPeople = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
const sortedUsers = unsortedPeople.sort((a, b) => a.name.localeCompare(b.name))
const final = sortedUsers.reduce((finalSoFar, user) => {
const thisUserFirstChar = user.name[0];
if (finalSoFar.length === 0) addHeader();
else {
const lastUserFirstChar = finalSoFar[finalSoFar.length - 1].name[0];
if (lastUserFirstChar !== thisUserFirstChar) addHeader();
}
finalSoFar.push(user);
return finalSoFar;
function addHeader() {
finalSoFar.push({ header: thisUserFirstChar });
}
}, []);
console.log(final);
Why don't you just keep track of the current abbreviation as you loop. Then you can add a head when it changes:
var users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}]
const sortedUsers = users.sort((a, b) => a.name.localeCompare(b.name))
var currentHeader
let final = sortedUsers.reduce((a, user) => {
if (currentHeader !== user.name[0]) {
currentHeader = user.name[0]
a.push({header: currentHeader})
}
a.push(user)
return a
},[])
console.log(final)
Here's one way to do it:
const users = [{name: 'Julia'}, {name: 'Ismeh'}, {name: 'Alison'}, {name: 'Andrea'}, {name: 'Betty'}];
let lastIndex;
let result = [];
users.sort((a, b) => {
return a.name > b.name;
}).forEach((user) => {
const index = user.name.charAt(0);
if (index !== lastIndex) {
result.push({
header: index
});
}
lastIndex = index;
result.push(user.name);
}, []);
console.log(result);
You can use _.orderBy(collection, [iteratees=[_.identity]], [orders]) and _.groupBy(collection, [iteratee=_.identity]) method of lodash.
This orderBy is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.
groupBy will creates an object composed of keys generated from the results of running each element of collection thru iteratee. The order of grouped values is determined by the order they occur in collection. The corresponding value of each key is an array of elements responsible for generating the key. The iteratee is invoked with one argument: (value).
example
// The `_.property` iteratee shorthand.
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
// Sort by `user` in ascending order and by `age` in descending order.
_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
With lodash
let myArr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
myArr = _.orderBy(myArr, ['name'], ['asc']);
let r = _.groupBy(myArr, o => {
return o.name.charAt(0).toUpperCase();
})
console.log(r);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
IN ES5
var arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}],
fChar = '';
arr = arr.sort(function(a, b) {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce(function(r, o) {
fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(arr);
IN ES6
const arr = [{
name: 'Julia'
}, {
name: 'Ismeh'
}, {
name: 'Andrea'
}, {
name: 'Alison'
}, {
name: 'Betty'
}];
let result = arr.sort((a, b) => {
a = a.name.toUpperCase(); // ignore upper and lowercase
b = b.name.toUpperCase(); // ignore upper and lowercase
return a < b ? -1 : (a > b ? 1 : 0);
}).reduce((r, o) => {
let fChar = o.name.charAt(0).toUpperCase();
if (!r[fChar]) {
r[fChar] = [];
}
r[fChar].push({
name: o.name
});
return r;
}, {});
console.log(result);

sorting when keys are not present javascript using localecompare

I have json like below
[
{name:'aa',age:'1Y',address:'Alaska'},
{name:'cc',age:'4Years',address:'California'},
{name:'mm',address:'Texas'}
]
Whenever I sort with name and address it work but it will throw runtime error if I try to sort with age as it is missing on last entry.
This is my attempt
let obj=[
{name:'aa',age:'2y',address:'Alaska'},
{name:'cc',age:'4y',address:'California'},
{name:'bb',address:'Texas'}
]
let field='age'; //work for name and address;
let mode='string';
if(mode!='number'){
console.log (obj.sort((a, b) => a[field].toString().localeCompare(b[field].toString())));
}
else{
console.log(obj.sort((a, b) => a[field] -b[field]))
}
What is the best way to ignore the entry when keys are not present , do I need to have seperate loop to check keys before sorting . Entry with missing keys will be on the bottom.
Ps: Ages are never >10 years from the business logic and they can come in any format like 1,1Y so it is treated as string
Just make sure you either have the value of the object, or return an empty string.
The shortest code path would be
(a[field] || "")
Where you indicate that if a doesn't have the property, it will treat it as an empty string.
It won't cover for a being null though, so if that can happen, you have to check it more carefully still
let obj = [{
name: 'aa',
age: '25',
address: 'Alaska'
},
{
name: 'cc',
age: '25',
address: 'California'
},
{
name: 'bb',
address: 'Texas'
}
]
let field = 'age'; //work for name and address
console.log(obj.sort((a, b) => (a[field] || "").toString().localeCompare((b[field] || "").toString())));
Another way to do this, would be to simply compare the values (note, again, if a or b would be null, there might be a problem)
let obj = [{
name: 'aa',
age: 25,
address: 'Alaska'
},
{
name: 'cc',
age: 3,
address: 'California'
},
{
name: 'bb',
address: 'Texas'
}
]
function sortAndPrint( obj, field ) {
console.log(`Sorting by ${field}`);
console.log(obj.sort((a, b) => a[field] > b[field] ) );
}
sortAndPrint(obj, 'name');
sortAndPrint(obj, 'address');
sortAndPrint(obj, 'age');
Entry with missing keys will be on the bottom
Ask for the current value to decide what will be compared or what will be at the bottom.
let obj=[{name:'aa',age:'2y',address:'Alaska'},{name:'cc',age:'4y',address:'California'},{name:'bb',address:'Texas'}],
field = 'age';
console.log(obj.sort((a, b) => a[field] ? b[field] ? a[field].toString().localeCompare(b[field].toString()) : -1 : 1));
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you want to compare the numbers within this string 10years or this string 5y, and so on, use a regex to compare the numbers.
let obj=[{name:'aa',age:'24y',address:'Alaska'},{name:'cc',age:'4years',address:'California'},{name:'bb',address:'Texas'}],
field = 'age';
console.log(obj.sort((a, b) => {
let evaluate = () => {
let aval = a[field].replace(/[^\d]/g, '').trim();
let bval = b[field].replace(/[^\d]/g, '').trim();
return aval !== '' && bval !== '' ? Number(aval) - Number(bval) : a[field].toString().localeCompare(b[field].toString());
};
return a[field] ? b[field] ? evaluate() : -1 : 1
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }
let obj=[
{name:'aa',age:'25',address:'Alaska'},
{name:'cc',age:'25',address:'California'},
{name:'bb',address:'Texas'}
]
let field='age'; //work for name and address
const sortFunc = (a, b) => a[field].toString().localeCompare(b[field].toString())
// If you can discard entries without the field
console.log(obj.filter(e => field in e).sort(sortFunc))
// If you cannot
console.log(obj.filter(e => field in e).sort(sortFunc).concat(obj.filter(e => !(field in e))))
This happens because on the last element you don't have the property age when you're trying to access it with property toString.(it is null with the age key)
Just in case you're looking for an overkill solution :)
let obj=[
{name: 'aa', age: 25, address: 'Alaska'},
{name: 'cc', age: 24, address: 'California'},
{name: 'mm', address: 'Texas'}
];
let field = 'age';
console.log (obj.sort((a, b) => {
a = a[field];
b = b[field];
let defaultValue = '';
if (typeof a === 'number' || typeof b === 'number') {
defaultValue = 0;
}
a = a || defaultValue;
b = b || defaultValue;
if (typeof a === 'number') {
return a - b;
}
else {
return a.localeCompare(b);
}
}));
This automatically handles either strings or numbers, sorting correctly for each. If you want the no-age entries to sort higher rather than lower than everything else, just change what defaultValue is set to for numbers to a large number.

Categories