I currently have this comparator function that sorts a custom struct using qsort, and I want to port it to std::sort, but it seems like it needs to return a bool value instead of -1, 0 and 1, how would I rewrite it?
int tsort = 1, sorta = 1;
static auto titleSort(const void *c1, const void *c2) -> int {
switch (tsort) {
case 0:
return ((Title *) c1)->listID - ((Title *) c2)->listID;
case 1:
return strcmp(((Title *) c1)->shortName, ((Title *) c2)->shortName) * sorta;
case 2:
if (((Title *) c1)->isTitleOnUSB == ((Title *) c2)->isTitleOnUSB)
return 0;
if (((Title *) c1)->isTitleOnUSB)
return -1 * sorta;
if (((Title *) c2)->isTitleOnUSB)
return 1 * sorta;
return 0;
case 3:
if (((Title *) c1)->isTitleOnUSB && !((Title *) c2)->isTitleOnUSB)
return -1 * sorta;
if (!((Title *) c1)->isTitleOnUSB && ((Title *) c2)->isTitleOnUSB)
return 1 * sorta;
return strcmp(((Title *) c1)->shortName, ((Title *) c2)->shortName) * sorta;
default:
return 0;
}
}
CodePudding user response:
As mentioned in a comment, the simple, quick, and dirty solution is
std::sort(first,last, [](const auto& a,const auto& b) {
return titleSort(&a,&b) < 0;
});
Because the comparator for std::sort
should return true
when a < b
and false
otherwise. When a < b
then titleSort
returns -1
and 0
or 1
otherwise. Hence you want to map 1
to true
and 0
and 1
to false
, and < 0
does that.
You also need to consider that the comparator for std::sort
must conform to the named requirement Compare. Typical comparisons do that, but when they don't then using them with std::sort
is undefined. I didn't find similar requirement for qsort
.
CodePudding user response:
If possible, get rid of int tsort = 1, sorta = 1;
and use directly the appropriate method.
The dispatch, if needed, can be done with something like (C 20):
void sortTitle(std::vector<Title>& title, int tsort = 1, sorta = 1)
{
switch (tsort)
{
case 0:
std::ranges::sort(titles, std::ranges::less{}, &Title::listID); break;
case 1: {
const auto proj = [](const Title& title){
return std::string_view(title.shortName);
};
if (sorta == 1) {
std::ranges::sort(titles, std::ranges::less{}, proj);
} else {
std::ranges::sort(titles, std::ranges::greater{}, proj);
}
break;
}
case 2:
if (sorta == 1) {
std::ranges::sort(titles, std::ranges::less{}, &Title::isTitleOnUSB);
} else {
std::ranges::sort(titles, std::ranges::greater{}, &Title::isTitleOnUSB);
}
break;
case 3: {
const auto proj = [](const Title& title){
return std::make_tuple(title.isTitleOnUSB,
std::string_view(title.shortName));
};
if (sorta == 1) {
std::ranges::sort(titles, std::ranges::less{}, proj);
} else {
std::ranges::sort(titles, std::ranges::greater{}, proj);
}
break;
}
}
}
CodePudding user response:
The pragmatic approach has a lot of upsides. You don't need to change much for example.
However, the old titleSort
did a lot of runtime checking of the tsort
and sorta
values. It would be better if the runtime values could be checked one time only (in runtime).
You could create a functor template that you instantiate with tsort
and sorta
as template parameters. If you'd like to keep all the logic in one place as before, you'd use constexpr-if to make the selection:
enum class tsort_t { zero, one, two, three };
template <tsort_t tsort, bool sorta = false>
struct titleSorter {
bool operator()(const Title& a, const Title& b) const {
if constexpr (tsort == tsort_t::zero) {
return b.listID < a.listID;
} else if constexpr (tsort == tsort_t::one) {
return std::strcmp(a.shortName, b.shortName) < 0;
} else if constexpr (tsort == tsort_t::two) {
if (a.isTitleOnUSB == b.isTitleOnUSB) return false;
if constexpr (sorta) {
return a.isTitleOnUSB;
} else {
return b.isTitleOnUSB;
}
} else if constexpr (tsort == tsort_t::three) {
if (a.isTitleOnUSB != b.isTitleOnUSB) {
if constexpr (sorta) {
return a.isTitleOnUSB;
} else {
return b.isTitleOnUSB;
}
} else {
return strcmp(a.shortName, b.shortName) < 0;
}
}
}
};
Disclaimer: I may have made some mistakes when converting the logic, but I think it's ok - double check it
And for checking the runtime values of tsort
and sorta
, you'd add a frontend which instantiates the proper titleSorter
:
template <class It>
void titleSort(It first, It last, int tsort, int sorta) {
switch (tsort) {
case 0:
std::sort(first, last, titleSorter<tsort_t::zero>{});
break;
case 1:
if (sorta == 1)
std::sort(first, last, titleSorter<tsort_t::one, true>{});
else
std::sort(first, last, titleSorter<tsort_t::one>{});
break;
case 2:
if (sorta == 1)
std::sort(first, last, titleSorter<tsort_t::two, true>{});
else
std::sort(first, last, titleSorter<tsort_t::two>{});
break;
case 3:
if (sorta == 1)
std::sort(first, last, titleSorter<tsort_t::three, true>{});
else
std::sort(first, last, titleSorter<tsort_t::three>{});
break;
default:
throw std::runtime_error("erroneous tsort algorithm selected");
}
}
Note: I would also consider changing shortName
to being a std::string
.