Home > Back-end >  sorting array and keeping only the last value of each day reactjs
sorting array and keeping only the last value of each day reactjs

Time:08-04

i'm having a bit of a problem here, i have an array that looks like this :

const input = {
  name: 'xy',
  lastname: 'yx',
  history: [
  { "value": 0.02, "date": "2022-08-02T23:03:22.895Z" }, 
  { "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
  { "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
  { "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
  { "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }]
}                            

but it have more data and more dates than this exemple, i want to sort it into a new array and put only the newest value of each day

so it will look like this :

const input = {
  name: 'xy',
  lastname: 'yx',
  history: [
   { "value": 0.02, "date": "2022-08-02T23:03:22.895Z" }, 
   { "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
   { "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
   { "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
   { "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }],                   
  newHistory: 
   { "value": 0.02, "date": "2022-08-02T23:03:22.895Z" }, 
   { "value": 0.02, "date": "2022-08-01T23:03:32.584Z" }],   
}

                        

i currently did the sorting of all the dates but i'm having trouble in doing the rest.

CodePudding user response:

Since you've already sorted your data you could use a single reduce to iterate over your array of sorted data while keeping track of a new output array.

For each data point, you compare its date by the last element of the output. If the year-month-day part is not the same, you add the data point to the output array.

Here are these steps written out in code:

const input = [
  { "value": 0.02, "date": "2022-08-02T23:03:22.895Z" }, 
  { "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
  { "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
  { "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
  { "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
  
const output = input.reduce(
  (out, current) => {
    const lastDate = out.at(-1)?.date.slice(0, 10);
    const currentDate = current.date.slice(0, 10);
    
    if (lastDate !== currentDate) {
      out.push(current);
    }
     
    return out;
  },
  []
);

console.log(output);

If you like one-liners, you could also write this as:

const input = [
  { "value": 0.02, "date": "2022-08-02T23:03:22.895Z" }, 
  { "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
  { "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
  { "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
  { "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
  
const output = input.reduce(
  (out, current) => out.at(-1)?.date.slice(0, 10) === current.date.slice(0, 10) ? out : out.concat(current),
  []
);

console.log(output);

CodePudding user response:

I think this is definitely a problem where splitting different tasks into functions helps make the code easier to understand.

You said in a comment that you wanted to group the calendar dates according to UTC time, but (if you change your mind) the code below will also allow you to optionally use the system time zone (as well as some other options).

I've included lots of comments to explain as you read, but feel free to ask for clarity in a comment if something still isn't clear.

'use strict';

/** @returns a stringified number with a minimum length */
function padN (n, maxLength = 2, fillString = '0') {
  return String(n).padStart(maxLength, fillString);
}

/**
 * @returns a default sorting algorithm function
 * for an array of objects each having a specific key
 */
function createDefaultSortByObjKey (key, {reverse = false} = {}) {
  return reverse
    ? (oA, oB) => oA[key] > oB[key] ? -1 : oA[key] < oB[key] ? 1 : 0
    : (oA, oB) => oA[key] < oB[key] ? -1 : oA[key] > oB[key] ? 1 : 0;
}

/**
 * Set the `utc` option to `true` to use UTC
 * @returns a date string format like `"20220703"`
 */
function getSerializedYMD (date, {utc = false} = {}) {
  const year = utc ? date.getUTCFullYear() : date.getFullYear();
  const month = utc ? date.getUTCMonth() : date.getMonth();
  const dayOfMonth = utc ? date.getUTCDate() : date.getDate();
  return `${year}${padN(month)}${padN(dayOfMonth)}`;
}

/** @pure */
function transform (array, {
  newestFirst = false,
  parseDates = false,
  utc = false,
} = {}) {
  // Create a function for sorting dates, sorting by oldest/newest
  const sortFn = createDefaultSortByObjKey('date', {reverse: newestFirst});

  const sorted = array
    // Create actual date objects for every date property value
    .map(o => ({...o, date: new Date(o.date)}))
    // Sort them
    .sort(sortFn);

  // This will be used to compare if the current object's date occurred
  // on the same date as the previous
  let lastKey = '';
  // The objects will be stored in inner arrays, grouped by calendar dates
  const grouped = [];

  for (const obj of sorted) {
    const key = getSerializedYMD(obj.date, {utc});
    // Create a new inner array group if a new calendar date is encountered
    if (key !== lastKey) grouped.push([]);
    // Add the object to the current date group
    grouped.at(-1).push(obj);
    // Update the last key
    lastKey = key;
  }

  // Now just pick one date from each group
  return grouped.map(group => {
    // Pick the oldest/newest object in the group
    const obj = group.at(newestFirst ? 0 : -1);
    return parseDates
      // Return it directly with the date value as an actual date object
      ? obj
      // Or convert the date back to an ISO string first
      : {...obj, date: obj.date.toISOString()};
  });
}

const input = {
  name: 'xy',
  lastname: 'yx',
  history: [
    { value: 0.02, date: '2022-08-02T23:03:22.895Z' },
    { value: 0.04, date: '2022-08-02T22:03:16.603Z' },
    { value: 0.08, date: '2022-08-02T21:03:20.378Z' },
    { value: 0.02, date: '2022-08-01T23:03:32.584Z' },
    { value: 0.04, date: '2022-08-01T22:03:30.311Z' },
  ],
};

const result = {
  // Spread in all of the other properties
  ...input,

  // And add a new transformed array from the `history` property
  newHistory: transform(input.history, {newestFirst: true, utc: true}),

  // You can even use the sorting function to sort the original history if you want:
  // history: [...input.history].sort(createDefaultSortByObjKey('date', {reverse: true})),
};

console.log(result);

// You could have also used the options to get a different result variation:
// transform(input.history, {
//   newestFirst: false,
//   parseDates: true,
//   utc: false,
// });

  • Related