C++ - Sorting strings in a vector in a specific way

Viewed 219

I'm currently attempting to sort strings in a vector, but I'm unsure on how to go about doing it a specific manner. The game engine I am working with requires strings to be sorted case-insensitively (which I have been able to accomplish). It also sorts these file names with underscores coming before letters.

For example, these strings are currently sorted as follows:

ChallengeBattleships.bsf
ChallengeBattleships.zip
ChallengeBattleshipsPause.bsf
ChallengeBattleshipsSummary.bsf
ChallengeBattleshipsSurface.bsf
ChallengeBattleships_arena.bsf
ChallengeBattleships_day.zip

But I need them to be sorted like this (as well as case-insensitively):

ChallengeBattleships.bsf
ChallengeBattleships.zip
ChallengeBattleships_arena.bsf
ChallengeBattleships_day.zip
ChallengeBattleshipsPause.bsf
ChallengeBattleshipsSummary.bsf
ChallengeBattleshipsSurface.bsf

Would anyone happen to know how to sort strings in a vector in this manner?

2 Answers

What you are asking for is a custom lexicographical ordering. One possibility would be to use the lexicographical comparison helper in #include <algorithm> which accepts custom comparators. E.g. something that loves underscores above all else :

#include <algorithm>
#include <iostream>

namespace {
std::vector<std::string> names = {
  "ChallengeBattleships_day.zip",
  "ChallengeBattleships.bsf",
  "ChallengeBattleshipsSummary.bsf",
  "ChallengeBattleshipsPause.bsf",
  "ChallengeBattleships_arena.bsf",
  "ChallengeBattleships.zip",
  "ChallengeBattleships_day.zip",
  "ChallengeBattleshipsSurface.bsf"
};

// custom char ordering
bool UnderscoreSupporter(char lhs, char rhs) {
  if (lhs == rhs) return false;
  if (lhs == '_') return true;  // '_' is smaller than everything !
  if (rhs == '_') return false; // nothing is smaller than '_' !
  
  return (lhs < rhs); // default lexicographical ordering
}
}

int main()
{

  std::cout << "Original order: " << std::endl;
  for (const auto& str : names) std::cout << str << std::endl;
  std::cout << std::endl;

  // lambda to compare strings using our custom ordering
  auto sorting_fct = [](const std::string& s1, const std::string& s2) {
    return std::lexicographical_compare(s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend(), UnderscoreSupporter);
  };

  // sort names based on that lambda
  std::sort(names.begin(), names.end(), sorting_fct);

  std::cout << "New order: " << std::endl;
  for (const auto& str : names) std::cout << str << std::endl;
  std::cout << std::endl;

  return 0;
}

I'll let you add the "ignore case" part, but that should be straight forward using e.g. tolower from <cctype>.

Use std::sort with lambda. If original strings are not to be modified then:

std::sort(vec.begin(), vec.end(), 
         [](std::string s1, std::string s2){
             std::transform(s1.begin(), s1.end(), s1.begin(),
                   [](char c){ return std::tolower(c); });
             std::transform(s2.begin(), s2.end(), s2.begin(),
                   [](char c){ return std::tolower(c); });
             return s1 < s2;
         });

To change case: How to convert std::string to lower case?

Related