1

I am trying to return a list of sorted products:

return products.sort(getSortFunc(activeSortId))

Each activeSortId corresponds to a sorting callback function which is retrieved by calling getSortFunc. I have three sorting function: sortByPriceAsc, sortByPriceDesc, sortByDate.

The problem: When the user sortByPriceDesc followed by sortByDate the result is (logically) sorted by descending price still. I would however like it to be sorted by ascending price. I could solve this by chaining the sorting function (return products.sort(a).sort(b).sort(c)), but what if I don't know the order?:

I'm wondering if I can solve it by calling e.g. sortByPriceAsc within the function sortByDate, or something similar? Below is a simplified attempt, but obviously it does not work because calling sortByPriceAsc doesn't modify anything:

sortByPriceAsc: (a, b) => a.price - b.price,
sortByDate: (a, b) => {
  this.sortByPriceAsc()
  return b.date - a.date
}

Glad for any help or general critique.

howtopythonpls
  • 770
  • 1
  • 6
  • 18
  • `I could solve this by chaining the sorting function` - in general, no, because `sort` isn't guaranteed to be stable. – georg Oct 05 '19 at 12:03

1 Answers1

0

One option is to have a persistent variable indicating the last asc/dec sort order. Then, in sortByDate, whenever it's called, check that variable to figure out what to return in the case the two dates are the same:

const arr = [
  { date: 1, price: 5 },
  { date: 2, price: 10 },
  { date: 2, price: 20 },
  { date: 3, price: 30 },
  { date: 3, price: 40 },
  { date: 4, price: 50 },
];

let lastWasAsc = false;
const sorts = {
  sortByPriceAsc: (a, b) => a.price - b.price,
  sortByDate: (a, b) => {
    return b.date - a.date || (
      lastWasAsc
      ? a.price - b.price
      : b.price - a.price
    );
  }
};

// When sorting by ascending order, set lastWasAsc to true:
lastWasAsc = true;
arr.sort(sorts.sortByPriceAsc);

// Then, later:
arr.sort(sorts.sortByDate);
console.log(arr);

Compare to, when lastWasAsc is false when sortByDate is called:

const arr = [
  { date: 1, price: 5 },
  { date: 2, price: 10 },
  { date: 2, price: 20 },
  { date: 3, price: 30 },
  { date: 3, price: 40 },
  { date: 4, price: 50 },
];

let lastWasAsc = false;
const sorts = {
  sortByPriceAsc: (a, b) => a.price - b.price,
  sortByDate: (a, b) => {
    return b.date - a.date || (
      lastWasAsc
      ? a.price - b.price
      : b.price - a.price
    );
  }
};

// Then, later:
arr.sort(sorts.sortByDate);
console.log(arr);

Keep in mind that arr.sort(cb1).sort(cb2) will not necessarily take into account anything sorted by cb1 - the sorting algorithm is not necessarily stable, so behavior will differ across implementations (and thus .sort(..).sort( should not be used).

(A stable sorting algorithm is when, when two elements are determined to be next to each other in the result due to having the same "weight", they are in the same order in the output that they were in the input. This is not guaranteed in Javascript. While ES2019 requires Array#sort to be stable, not all implementations comply with that yet.)

str
  • 42,689
  • 17
  • 109
  • 127
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Reading `return b.date - a.date || (lastWasAsc ? a.price - b.price : b.price - a.price);` out loud make it seem like it either sorts by date or by price, I guess this is not the case? So basically we sort by date, then also change the order of the price if it was *ascending* last? I'm just trying to understand what the `sortByDate` do. Thanks so much for your reply! – howtopythonpls Oct 05 '19 at 18:52
  • That conditional operator means: if the date difference comes out to 0, then return the following: if `lastWasAsc`, return `a.price - b.price`, else return `b.price - a.price`. It only sorts by relative price between two items *if* there is no difference in those two items' dates. – CertainPerformance Oct 05 '19 at 21:35
  • I don't completely understand. If it only does this part: `(lastWasAsc ? a.price - b.price : b.price - a.price)` if the date difference is 0 then it only does this when the dates are the same? No dates are the same in your example so it would seem as that part of the code would never execute. – howtopythonpls Oct 05 '19 at 22:18
  • There are two pairs of objects with the same date in the example: `{ date: 2, price: 10 }, { date: 2, price: 20 }`, and `{ date: 3, price: 30 }, { date: 3, price: 40 }`. In the first snippet, the objects with the lower price come first, whereas the reverse is true for the second snippet - which was the crux of your question (the snippets look to produce your desired functionality, right?). – CertainPerformance Oct 05 '19 at 22:20
  • Your code seem to do the right thing, but I don't understand it enough to reproduce it. I actually mixed up some function names in the questions. What I want is the result to be sorted in ascending order when the date function is applied (the other way around). I can't seem to adjust your conditional statement to achieve that.. – howtopythonpls Oct 05 '19 at 22:37
  • It's not really clear, do you mean that you want the results to be primarily in order by price, and then, if there are items with the same price, to sort those two items relative to themselves by date? – CertainPerformance Oct 05 '19 at 22:56
  • Sorry, I got confused so I communicated unclearly. I see now how your answer can help. It is when the dates are equal that I would like to sort back the results to ascending if they where previously descending. This is exactly why your answer makes sense, correct me if I'm wrong :P I will accept your answer! – howtopythonpls Oct 05 '19 at 23:08