Amir
Amir

Reputation: 721

Sort array by month and year

I need help with sorting an array by month and year to display on chart respectively.

Array1: ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'....];
Desired Output : [....'Apr18','May18','Jun18','Jul18','Jan19','Mar19'];

I also have another array with values for each month according to the array above respectively

Array2: ['Mar19_value','Apr18_value','Jun18_value','Jul18_value','May18_value'
       ,'Jan19_value'....];

Array2: ['55','2','3','0','21','132'....]; //real values

When the monthyear array sorts, I want the data in this array to move to a new position according to the monthyear position. Like this:

Desired Array1: [....'Apr18','May18','Jun18','Jul18','Jan19','Mar19'];
Desired Array2: [....'Apr18_value','May18_value','Jun18_value','Jul18_value','Jan19_value','Mar19_value'];

So I can pick the data later like this:

var label = array[4];
var value = array2[4];

How can this be accomplished?

Upvotes: 21

Views: 5143

Answers (9)

KAMLESH NEHRA
KAMLESH NEHRA

Reputation: 1

You can try something like this:

var Array =  ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'];
Array.sort(function(a, b) {
    a = [a.slice(0,3), ' 20', a.slice(3)].join('');
    b = [b.slice(0,3), ' 20', b.slice(3)].join('')
    return new Date() - new Date(b);
});
            
console.log(Array);

Upvotes: 3

prasanth
prasanth

Reputation: 22500

You need change the string to new Date(dateString) format like

new Date(Month Date Year)

Updated regex Pattern for Both Array

https://regex101.com/r/h1cm1z/2/

Updated Sort Second array based on first array sorting index

var arr1 =  ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'];
var arr2 =['55','2','3','0','21','132'];

function datesort(arr){
 return arr.concat().sort((a,b)=>{
 a = a.replace(/(\d+)(.*)/g,' 1 $1'); // Month 1 YEAR
 b = b.replace(/(\d+)(.*)/g,' 1 $1'); // Month 1 YEAR
  return new Date(a) - new Date(b)
})
}

var after_arr1 =new datesort(arr1);
var after_arr2 = arr1.reduce(function(a,b,c){
     var ind = after_arr1.indexOf(b);
     a[ind] = arr2[c]
     return a
},[]);


console.log(after_arr1.join(','));
console.log(after_arr2.join(','))

Upvotes: 14

Nina Scholz
Nina Scholz

Reputation: 386654

You could get the date as a sortable string and sort it.

For getting more than one array sorted by one signature array, you could take sorting with map, where you sort an array of indices, indicating the final sorting and then reassign all arrays with this sorting.

The getD function returns a formatted string by taking an index of array0 for sorting. Inside of the function the string is destructed into month and year parts, and replaced by its ISO 8601 representation. The replacement function callback takes the matched items, returns an array with a formatted year and the month of an object with the month names and the related month numbers. Then, this array is joined and returned.

Sorting takes place with a comparison with String#localeCompare.

var array1 = ['Mar19', 'Apr18', 'Jun18', 'Jul18', 'May18', 'Jan19'],
    array2 = ['Mar19_value', 'Apr18_value', 'Jun18_value', 'Jul18_value', 'May18_value', 'Jan19_value'],
    array3 = ['55', '2', '3', '0', '21', '132'],
    indices = Object
        .keys(array1)
        .sort(function (a, b) {
            function getD(i) {
                var months = { Jan: '01', Feb: '02', Mar: '03', Apr: '04', May: '05', Jun: '06', Jul: '07', Aug: '08', Sep: '09', Oct: '10', Nov: '11', Dec: '12' },
                    s = array1[i];
                return s.replace(/^(...)(.+)$/, (_, m, y) => [y.padStart(4, '0'), months[m]].join('-'));
            }
            return getD(a).localeCompare(getD(b));
        }),
    result = [array1, array3].map(a => indices.map(i => a[i]));
  
result.forEach(a => console.log(...a));

Upvotes: 12

Scott Sauyet
Scott Sauyet

Reputation: 50797

I would suggest that your data structure is unfortunate. Working with shared indices makes it much more difficult than a data structure that combines them, such as an array of objects, [{label: 'Mar19', value: 55}, ...].

If your data is coming from an upstream solution you cannot control, you can still manage this in your own work, converting before you use it. (And if you really have to converting back to pass to others.)

A common name for a function combining two arrays is zip -- think of it acting like a zipper. This version uses one that takes a function to say how the paired elements should be combined. (Elsewhere such a function might be called zipWith.)

Here sortArraysByDate calls zip passing a function that turns 'Mar19' and 55 into {label: 'Mar19, value: 55, month: 'Mar', year: 19} using dateFields to extract the month and year from that label, then sorts these using the straightforward dateSort

const months = {Jan: 1, Feb: 2, Mar: 3, Apr: 4,  May: 5,  Jun: 6,
                Jul: 7, Aug: 8, Sep: 9, Oct: 10, Nov: 11, Dec: 12}
                
const dateFields = (d) => ({
  month: d.slice(0, 3),
  year: Number(d.slice(3))
})

const dateSort = ({month: m1, year: y1}, {month: m2, year: y2}) =>
  (y1 - y2) || (months[m1] - months[m2])

const zip = (fn, a1, a2) => a1.map((a, i) => fn(a, a2[i]))

const sortArraysByDate = (a1, a2) =>
  zip((label, value) => ({label, value, ...dateFields(label)}), a1, a2)
    .sort(dateSort)
    .map(({label, value}) => ({label, value}))


const Array1 = ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'];
const Array2 =   ['55','2','3','0','21','132']; //real values

const result = sortArraysByDate(Array1, Array2)

console.log(result)

The map call in sortArraysByDate is quite possibly not necessary. Without it, the resulting data includes extra year and month fields; that might not be an issue.

If you really need those updated arrays in the original format, you can just map the result:

const newArray1 = result.map(o => o.label)
const newArray2 = result.map(o => o.value)

But I would urge you not to do this unless it's absolutely necessary. This structure is really useful. The paired arrays are much less so.

Also, if you need to combine more than two arrays, you could write a slightly more sophisticated version of zip:

const zip = (fn, ...as) => as[0].map((_, i) => fn(...as.map(a => a[i])))

This takes a function on n arguments, and n arrays, and yields a new array containing the result of calling that function respectively on the successive items in each of the arrays.

Upvotes: 2

simbathesailor
simbathesailor

Reputation: 3687

const input = ['Mar19', 'Apr18', 'Jun18', 'Jul18', 'May18', 'Jan19'];

//const output : ['Apr18','May18','Jun18','Jul18','Jan19','Mar19'];
//Can be done easily by using momentjs, darte-fns, but here i will do it natively

const t = {
  Jan: 1,
  Feb: 2,
  Mar: 3,
  Apr: 4,
  May: 5,
  Jun: 6,
  Jul: 7,
  Aug: 8,
  Sep: 9,
  Oct: 10,
  Nov: 11,
  Dec: 12
}
const giveConcatString = (a, t) => {
  const monthPart = a.substr(0, 3)
  const yearPart = a.substr(3)
  return `${yearPart}${t[monthPart]}`
}
const sortedArray = input.sort((a, b) => {
  const concatString = giveConcatString(a, t)
  const concatStringB = giveConcatString(b, t)
  return concatString <= concatStringB ? -1 : 1

})
console.log(sortedArray)

This may help you solve this problem. Did it natively.

Upvotes: 9

Jordan Maduro
Jordan Maduro

Reputation: 998

To order properly you need to know the order of the months. This is not alphabetical so you can use an array with the order of the months then look them up.

const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

Is already properly sorted and can be used in conjunction with .indexOf() to get the position of a month.

const myArr =  ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'];
const myArr2 = ['Mar19_value','Apr18_value','Jun18_value','Jul18_value','May18_value'
   ,'Jan19_value'];

const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

let sortYearMonth = (a, b) => {
    let monthA = monthOrder.indexOf(a.slice(0,3))
	let yearA = a.slice(3,6)
	let monthB = monthOrder.indexOf(b.slice(0,3))
	let yearB = b.slice(3,6)
	return (`${yearA}-${monthA}` < `${yearB}-${monthB}`) ? -1 : (`${yearA}-${monthA}` > `${yearB}-${monthB}`) ? 1 : 0
}

let sortedMonths = myArr.sort(sortYearMonth)
let sortedMonths2 = myArr2.sort(sortYearMonth)

console.log(sortedMonths )
console.log(sortedMonths2 )

Update: values in same position

Updated version links the two arrays together then sorts the first while keeping the relative position to the second.

Idea: Link two arrays with a temporary Object then extract the key/value pair using Object.entries. Then sorting the array based on the first value of the pair, which is the value of array1. Then it returns the key/value pair in the right order you can extract the values into two arrays again by using .map()

I added a run with the string based example and the real values examples below

const myArr = ['Mar19', 'Apr18', 'Jun18', 'Jul18', 'May18', 'Jan19'];
const myArr2 = ['Mar19_value', 'Apr18_value', 'Jun18_value', 'Jul18_value', 'May18_value', 'Jan19_value'];

const myArr3 = ['55','2','3','0','21','132'];

const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

let sortYearMonth = (a, b) => {
  let monthA = monthOrder.indexOf(a.slice(0, 3))
  let yearA = a.slice(3, 6)
  let monthB = monthOrder.indexOf(b.slice(0, 3))
  let yearB = b.slice(3, 6)
  return (`${yearA}-${monthA}` < `${yearB}-${monthB}`) ? -1 : (`${yearA}-${monthA}` > `${yearB}-${monthB}`) ? 1 : 0
}

function sortByFirst(myArr, myArr2) {
  let keyValue = myArr.reduce((links, item, i) => {
    links[item] = myArr2[i];
    return links
  }, {})
  let entries = Object.entries(keyValue)
  return entries.sort((a, b) => sortYearMonth(a[0], b[0]))
}

let sortedEntries = sortByFirst(myArr, myArr2)
let sortedMonths = sortedEntries.map(i => i[0])
let sortedValues = sortedEntries.map(i => i[1])

let sortedEntries2 = sortByFirst(myArr, myArr3)
let sortedMonths2 = sortedEntries2.map(i => i[0])
let sortedValues2 = sortedEntries2.map(i => i[1])

console.log(sortedMonths)
console.log(sortedValues)

console.log(sortedMonths2)
console.log(sortedValues2)

Upvotes: 9

Rahul
Rahul

Reputation: 18557

You can try this raw snippet.

var MY = ['Mar19', 'Apr18', 'Jun18', 'Jul18', 'May18', 'Jan19'];
var s = "JanFebMarAprMayJunJulAugSepOctNovDec";
// converting to raw date format
for (i in MY) {
  num = MY[i].match(/\d+/g);
  letr = MY[i].match(/[a-zA-Z]+/g);
  MY[i] = (s.indexOf(letr) / 3 + 1) + '-' + '20' + num;
}
// sorting logic
var sorted = MY.sort(function(a, b) {
  a = a.split("-");
  b = b.split("-")
  return new Date(a[1], a[0], 1) - new Date(b[1], b[0], 1)
});
// converting back to original array after sorting
res = [];
for (i in sorted) {
  var a = sorted[i].split('-');
  res[i] = (s.substr((parseInt(a[0]) - 1) * 3, 3)) + '-' + a[1].substr(2, 2);
}

console.log(res);

Upvotes: 4

Edper
Edper

Reputation: 9322

let mth = ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'];

mth.sort((itemA, itemB)=>{
   let mthAstr = itemA.slice(0,3)+" 01 "+itemA.slice(3,5);
   let mthA = new Date(mthAstr);
   let mthBstr = itemB.slice(0,3)+" 01 "+itemB.slice(3,5);
   let mthB = new Date(mthBstr);
   if (mthA < mthB)
      return -1;
   if (mthA > mthB)
      return 1;
   return 0;
});

Upvotes: 3

Krzysztof Krzeszewski
Krzysztof Krzeszewski

Reputation: 6749

I used lodash for sorting and substring method to divide date into month and day parts

const data = ['Mar20','Mar21', 'Mar01', 'Mar19','Apr18','Jun18','Jul18','May18','Jan19']

const monthMap = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

const sorted = _.sortBy(data, date => monthMap.indexOf(date.substring(0,3)), date => date.substring(3,5))

console.log(sorted)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

Upvotes: 3

Related Questions