'Sort/order a string array by two conditions (name sequence and date)- JavaScript
Having some issues trying to sort/order this string array. There are thousands of files names that come back in the response, below is an example of 10.
array = [
'ORDERHEADER_010122.arc',
'ORDERITEM_010122.arc',
'ORDERDETAIL_010122.arc',
'ORDERDETAIL_010222.arc',
'ORDERDETAIL_010322.arc',
'ORDERHEADER_010222.arc',
'ORDERHEADER_010322.arc',
'ORDERHEADER_010422.arc',
'ORDERITEM_010222.arc',
'ORDERDETAIL_010422.arc'
];
A simple array.sort() takes care of half of the issue as it will alphabetize the strings and inherently sort the dates.
What am needing is a "sequence" order of sorts along with the date order. So prioSequence = ['ORDERHEADER', 'ORDERDETAIL', 'ORDERITEM']; would be the sequence I want to see.
Expected output as:
array = [
'ORDERHEADER_010122.arc',
'ORDERDETAIL_010122.arc',
'ORDERITEM_010122.arc',
'ORDERHEADER_010222.arc',
'ORDERDETAIL_010222.arc',
'ORDERITEM_010222.arc',
'ORDERHEADER_010322.arc',
'ORDERDETAIL_010322.arc',
'ORDERHEADER_010422.arc',
'ORDERDETAIL_010422.arc'
];
Any help/guidance would be greatly appreciated! Thanks!
Solution 1:[1]
Prefix the string with the parts that determine the sort, i.e. yymmdd and 2 letters from the "ORDER" string, as it turns out that when you pick the 7th and 8th letter of those words (EA, ET, TE), they will be sorted correctly. Then after having sorted the items, remove that prefix again.
Here is how that works out:
let array = [
'ORDERHEADER_010122.arc',
'ORDERITEM_010122.arc',
'ORDERDETAIL_010122.arc',
'ORDERDETAIL_010222.arc',
'ORDERDETAIL_010322.arc',
'ORDERHEADER_010222.arc',
'ORDERHEADER_010322.arc',
'ORDERHEADER_010422.arc',
'ORDERITEM_010222.arc',
'ORDERDETAIL_010422.arc'
];
let sorted = array.map(item =>
item.replace(/ORDER.(..).*?_(..)(..)(..).*/g, "$4$3$2$1") + item
).sort().map(s => s.slice(8));
console.log(sorted);
Extending it
If you have more prefix-words of which you want to control the order, then create an array of those in their expected order. The solution will then turn that array into a lookup map (giving a 4-character sequence number for a given word). The call to replace then needs a callback argument, that will make the lookup and prefixes that sequence. Here is the code for that:
let array = [
'ORDERHEADER_010122.arc',
'ORDERITEM_010122.arc',
'ORDERDETAIL_010122.arc',
'ORDERDETAIL_010222.arc',
'ORDERDETAIL_010322.arc',
'ORDERHEADER_010222.arc',
'ORDERHEADER_010322.arc',
'ORDERHEADER_010422.arc',
'ORDERITEM_010222.arc',
'ORDERDETAIL_010422.arc'
];
let priorities = [
'ORDERHEADER',
'ORDERDETAIL',
'ORDERITEM',
];
// Map the priority array to an object for faster look-up
let priMap = Object.fromEntries(priorities.map((word, i) =>
[word, ("000" + i).slice(-4)]
));
let sorted = array.map(item =>
item.replace(/(.*?)_(..)(..)(..).*/g, (all, word, dd, mm, yy) =>
yy + mm + dd + (priMap[word] ?? "----") + all
)
).sort().map(s => s.slice(10));
console.log(sorted);
Solution 2:[2]
You have to define a custom compare function for your sorting method call. And, that method should first compare dates, and then (if a date is the same) order prefixes according to your requirements
Here is my example
const order = new Map() // one can use plain Array + Array#indexOf later on
.set('ORDERHEADER', 0)
.set('ORDERDETAIL', 1)
.set('ORDERITEM', 2)
const compareFn = (a, b) => {
const [a1, a2] = a.split('_')
const [b1, b2] = b.split('_')
const r2 = a2.localeCompare(b2) // TODO: re-write to date comparison a2 vs b2
if (r2 !== 0) return r2
return order.get(a1) - order.get(b1) // or Array#indexOf as mentioned above
}
// usage
array.sort(compareFn)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 |
