-1

So I have an array with the following structure:


 typedef struct {
   int order_num;
   string order_day; //Sort
   string client;
   string tech_type; 
   int serial_key;
   long problem;
   string technician_name;
   string tech_fix;
   int price;
   int days_spent;
   string status;
   string order_type;
   int problems_num;
   faults problems[10];
   }tech_info;

The customer provides data for the second field in format dd/mm/yyyy. I need to sort the array via that input. Here is what I have so far:


bool compare(const Date& d1, const Date& d2)
{
// All cases when true should be returned
if (d1.year < d2.year)
    return true;
if (d1.year == d2.year && d1.month < d2.month)
    return true;
if (d1.year == d2.year && d1.month == d2.month &&
    d1.day < d2.day)
    return true;

// If none of the above cases satisfy, return false
return false;

}

tech_info sort_date(tech_info* all_orders[]) {
vector<string> date;
string temp;
stringstream ss(temp);
for (int i = 0; i < counter; i++) {
    temp = all_orders[i]->order_day;
    while (ss.good()) { //Seperate date from '/' character
        string substr;
        getline(ss, substr, '/');
        date.push_back(substr);
    }

}

}

With this hopefully I'll be able to sort the date for every entry into a string. What would be the next step? How can I use this vector that holds the date information to sort the initial array? Thank you.

  • 1
    FYI: This is much simpler: `return std::tie(d1.year, d1.month, d1.day) < std::tie(d2.year, d2.month, d2.day);`. Instead of all of those `if` statements, one call to a comparison of `std::tie` values. – PaulMcKenzie Dec 08 '22 at 14:21
  • 1
    While only C++ is tagged, you're writing very C-like code. – sweenish Dec 08 '22 at 14:21
  • 1
    you have a vector of string to be sorted and you have a function that compares `Date`s. What you need now is a way to convert a string into a `Date`. Once you have that you are ready to call `std::sort` – 463035818_is_not_an_ai Dec 08 '22 at 14:39

1 Answers1

3

To compare dates given as strings of the form dd/mm/yyyy you would first want to transform them into a range of the form {yyyy, mm, dd} and then compare this range lexicographically. In other words, you can solve your problem by first solving three subtasks:

  1. You split your date string at / to obtain a range {dd, mm, yyyy}.
  2. You reverse the words in the splitted range {yyyy, mm, dd}
  3. You compare the splitted and reversed range lexicographically.

Here is a solution using C++20 and ranges:

#include <string>
#include <array>
#include <algorithm>
#include <vector>
#include <ranges>
#include <iostream>

struct tech_info {
    std::string ordering_day;
    // ... other stuff not relevant for question
};

auto split_and_reverse(std::string_view const& str)
{   
    return str |                                     // example: 31/12/1999
        std::views::reverse |                        // example: 9991/21/13
        std::views::split('/') |                     // example: {9991, 21, 13}
        std::views::transform(std::views::reverse) | // example: {1999, 12, 31}
        std::views::transform([](auto&& rng){
            return std::string_view{rng.begin(), rng.end()};
        });  // converts inner ranges to string_views
};

void sort_by_ordering_day(auto&& rng)
{
    std::ranges::sort(
        rng,
        std::ranges::lexicographical_compare,
        [](auto const& ti) { 
            return split_and_reverse(ti.ordering_day); 
        }
    ); 
};


int main() {
    std::array<tech_info, 5> tech_infos {{
        { "31/12/1999" },
        { "25/11/1999" },
        { "31/12/2022" },
        { "13/12/2012" },
        { "01/01/1844" }
    }};

    sort_by_ordering_day(tech_infos);

    for (auto const& ti : tech_infos){
        std::cout << ti.ordering_day << std::endl;
    }
}

Output:

01/01/1844
25/11/1999
31/12/1999
13/12/2012
31/12/2022

https://godbolt.org/z/n18Yd4bhe

You probably want to add some error checking to split_and_reverse if your input string conforms to your expected format. This alone justifies the creation of a dedicated Date class, or you use the ones provided by the standard library: https://en.cppreference.com/w/cpp/chrono/year_month_day. C++20 even comes with a function std::chrono::parse to parse instances of calendar classes from strings that offers error checking, but as of now not many compilers support it. Example: https://godbolt.org/z/3jnqfa373.

If you can't use C++20, I will leave solving the three subtasks as an exercise to you. You only really have to add bigger changes the split_and_reverse function:

  • sort_by_ordering_day uses std::ranges::sort which can be replaced by std::sort.
  • std::ranges::lexicographical_compare can be replaced by std::lexicographical_compare.
  • If you can't use C++17 you would also have to replace all occurances of std::string_view with std::string.
joergbrech
  • 2,056
  • 1
  • 5
  • 17
  • if I read this correctly then the strings are splitted for every comparison. Wouldnt it be better to first split them and then sort? – 463035818_is_not_an_ai Dec 09 '22 at 08:45
  • Sure! That would require storing the splitted date somewhere so it's a trade-off between memory and time cost. I have to admit I just assumed that neither time nor memory requirements were super tight. – joergbrech Dec 09 '22 at 09:13
  • BTW, I modified the answer to make the `split_and_reverse` call part of the projection not the comparator in the call to `std::ranges::sort`. This simplifies the code a bit. Also with this, `std::ranges::sort` would at least get the possibility of caching the projection result. But I just checked: It doesn't do that. The number of `split_and_reverse` calls remains the same. – joergbrech Dec 09 '22 at 10:16
  • 1
    afaik it could but doesnt. related: https://stackoverflow.com/questions/74694393/can-ranges-min-recalculations-be-avoided/74694525#74694525 – 463035818_is_not_an_ai Dec 09 '22 at 10:19