#Force human-like order in std::map

84 messages · Page 1 of 1 (latest)

snow bluff
#

I have a std::map<std::string, std::string>.
It performs fine, but there is a slight issue with sorting: it sorts entries like this:```
val1
val10
val11
...
val2
val20
val21
...
val3

However I want it to sort them in normal order:```
val1
val2
...
val9
val10
val11
val12
...
val19
val20
```I assume I need to write my own `std::less` definition for it? Or is there an existing class for this?
My current solution is somewhat... messy```cpp
struct NaturalOrder {
    bool operator() (
        const std::string& a,
        const std::string& b
    ) const noexcept {
        std::size_t i = 0;
        std::size_t j = 0;

        while (i < a.size() && j < b.size()) {
            // If both are digits, compare the numeric values
            if (std::isdigit(a[i]) && std::isdigit(b[j])) {
                std::size_t start_i = i;
                std::size_t start_j = j;

                // Skip leading zeros for fair comparison
                while (i < a.size() && a[i] == '0') ++i;
                while (j < b.size() && b[j] == '0') ++j;

                // Parse the numeric values
                while (i < a.size() && std::isdigit(a[i])) ++i;
                while (j < b.size() && std::isdigit(b[j])) ++j;

                // Compare the lengths of the numeric parts
                std::size_t len_a = i - start_i, len_b = j - start_j;
                if (len_a != len_b) return len_a < len_b;

                // Compare the numeric values digit by digit
                int cmp = a.substr(start_i, len_a).compare(b.substr(start_j, len_b));
                if (cmp != 0) return cmp < 0;
            } else {
                // If not digits, compare character by character
                if (a[i] != b[j]) return a[i] < b[j];
                ++i;
                ++j;
            }
        }

        // If one string has remaining characters, it is considered greater
        return a.size() < b.size();
    }
}
warm pathBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

snow bluff
#

Force human-like order in std::map

silk pine
#

What you can do is alternate between comparing text and number using
std::find_if and either std::isalpha or std::isdigit to find the end of the current part (and acting as the start of the next part)

Then for text part, use std::lexicographical_three_way_compare
If the result is not equal, return the relevant boolean, otherwise move to the next part.

For the numerical part, convert the substring into a number and use <=> and if the result is not equal, return the relevant boolean. Otherwise continue to the next part

#

This requires a decent level of comfort with c++ iterators

snow bluff
#

uh...

#

I feel so confused rn

placid galleon
# snow bluff I feel so confused rn

Basically:

  • Find first digit in the string
  • Convert all digits from there on to the number (you can use e.g. std::strtol for that)
  • Then compare the number you just extracted
  • If the numbers were equal, compare the text part up to the first digit you initially found
snow bluff
#

and do that for every number in a string

placid galleon
#

wdym every number?

#

There is only one number per string, no?

snow bluff
#

there could be more

placid galleon
#

Okay, then repeat steps 1-3 until there are no more numbers in your text.
Then I guess remove all the numbers so that you only have the text left, then compare the leftover text

snow bluff
#

that does the job for one numberset in a text, didnt check for others and dont know how it will behave for them

placid galleon
snow bluff
#

I mean, I somewhat know it works. At most the order would be messed up in edge cases

placid galleon
#

@snow bluff Does this help you in any way, shape or form?

#

;compile -Wall -Wextra -Wconversion -Wno-unused-parameter

#include <map>
#include <string>
#include <iostream>
#include <regex>

struct NaturalOrder {
    bool operator()(const std::string& a, const std::string& b) const noexcept {
        auto characters_regex = std::regex("\\D+");
        auto digits_regex = std::regex("\\d+");

        std::string a_csv = std::regex_replace(a, characters_regex, ",");
        std::cout << "a_csv : " << a_csv << "\n";

        std::string a_text = std::regex_replace(a, digits_regex, ",");
        std::cout << "a_text: " << a_text << "\n";
        return true;
    }
};

int main() {
    std::map<std::string, int, NaturalOrder> data;
    data["val1"] = 0;
    data["val2"] = 0;
    data["val10"] = 0;
    data["val1text3"] = 0;
}
livid socketBOT
#
Program Output
a_csv : ,1
a_text: val,
a_csv : ,1
a_text: val,
a_csv : ,2
a_text: val,
a_csv : ,1
a_text: val,
a_csv : ,1
a_text: val,
a_csv : ,10
a_text: val,
a_csv : ,1
a_text: val,
a_csv : ,1
a_text: val,
a_csv : ,1,3
a_text: val,text,
knotty ledge
#

just use find_if_not with reqursive iterator

#

because it can be something245ok1

#

ofc if u need compare 245 too then u need different aprouch

placid galleon
knotty ledge
#

like even between words?

placid galleon
#

And after that, if all the numbers were equal, somehow compare the text parts (again not sure if they should be split or merged and no clue how OP wants to compare them)

placid galleon
knotty ledge
#

looks sussy

snow bluff
#

yup, I want to order it like a human would. From 0 to 10, each word, etc.

#

not really sussy

#

just very... complex

knotty ledge
#

i mean

#

first what u need

placid galleon
#

@snow bluff Please answer that question: #1309899465552826429 message
And if it doesn't help you please also EXPLAIN why. As of now you've been EXTREMELY vague on what you want to do

knotty ledge
#

it find then with find_if with normal iterators and then complare strings from begin to this iterator and if not then next begin = to that iterator

#

i will make it really quick

placid galleon
knotty ledge
#

just bool if it numbers then if it string 😎

#

so find_if digit and then find_if alha

#

i will show

#

it hard to explain

placid galleon
#

Yeah, please

snow bluff
# placid galleon <@300238001299259412> Please answer that question: https://discord.com/channels/...

I thought I was being very explicit. Im sorry.
As for the question, I dont see if it would be of any help unfortunately. Although regex might be a good idea here.

Basically, I want to have a map with normal, human-like order.
Computer orders it for each character, but we take the whole string into consideration.
Take a look at this simplified regex:
([a-z]*[0-9]*)+
A computer would order it in this way:```
a1
a10
a11
a2
a20
a21
a22
a3

A human would order it in this way:```
a1
a2
a3
a10
a11
a20
a21
a22
#

I hope you understand where I am going with this

placid galleon
warm pathBOT
#
Tracer

there could be more

From #1309899465552826429 [[Jump to message]](#1309899465552826429 message)

placid galleon
#

In your example you only ever have a single number per string

snow bluff
#

in the above example there could be more than a single set of numbers.
Indeed, I didnt explicitly include multi number strings but its trivial to visualise how it would work. The same thing as you said before:

warm pathBOT
#
Monke

Okay, then repeat steps 1-3 until there are no more numbers in your text.
Then I guess remove all the numbers so that you only have the text left, then compare the leftover text

From #1309899465552826429 [[Jump to message]](#1309899465552826429 message)

placid galleon
snow bluff
#

that output is off, I'd assume it replaces numbers in one variable and text in the second one

knotty ledge
#

do u count words inside?

placid galleon
#

I showed you a way to extract all the numbers (or strings) and store them as a comma-separated string.
All you need to do is convert everything between the commas, then compare it.

Converting text to numbers and splitting a string should be very easily doable, hell there's definitly a SO comment explaining that.
Oh look, I found a SO comment explaining splitting a string by a delimiter 🐒 : https://stackoverflow.com/a/67081899

auto split(const std::string& str, char delimiter) -> std::vector<std::string>
{
    const auto range = str | 
                       std::ranges::views::split(delimiter) | 
                       std::ranges::views::transform(to_string);

    return {std::ranges::begin(range), std::ranges::end(range)};
}

You can google on how to convert a string of digits to a number yourself

snow bluff
#

I didnt think to split the string yet. My main idea was to iterate through the string, check for numbers and repeat

knotty ledge
#

do it count symbols or only alnum?

snow bluff
#

I didnt think of symbols. Only alnum

knotty ledge
#

ok

#

give me more 15 minutes

#

i not really familiar with new syntax of c++

#

how convert temporary string_view to integer

#

😭

snow bluff
#

stoi(string_view.data())

knotty ledge
#

a

#

make sence

placid galleon
knotty ledge
#
bool compare(const std::string& text1, const std::string& text2)
{

    std::string::const_iterator begin1 = text1.begin();
    std::string::const_iterator begin2 = text2.begin();

    std::variant<int, std::string_view> compare1, compare2;
    for(bool finddigit = isalpha(*begin);;finddigit = !finddigit)
    {
        std::string::const_iterator end1 = std::find_if(begin1, text1.end(), finddigit ? isdigit : isalpha);
        std::string::const_iterator end2 = std::find_if(begin2, text2.end(), finddigit ? isdigit : isalpha);
        if(finddigit)
        {
            compare1 = std::string_view(begin1, end1);
            compare2 = std::string_view(begin2, end2);
        }
        else
        {
            compare1 = std::stoi(std::string_view(begin1, end1).data());
            compare2 = std::stoi(std::string_view(begin2, end2).data());
        }
        if(end1 == text1.end() || end2 == text2.end()) break;
        if(compare1 == compare2) continue;
        break;
    }
    return compare1 < compare2;
}
knotty ledge
#

like im tiread so my code is cursed as hell

#

but i think main point is represented

placid galleon
snow bluff
#

no

knotty ledge
#

no?

#

what do u mean

placid galleon
#

Lmfao, why did you continue the thread as if you owned it then?

knotty ledge
#

because chad

snow bluff
knotty ledge
#

btw i need send my code to #cursed-code

#

so fix for your need because im already just jj