Пользовательский компаратор для целых чисел в векторе строки

Я хочу отсортировать вектор строк, представляющих очень большие значения (10**6 цифр) в порядке возрастания в C++. Я пытаюсь использовать собственный компаратор, но сортировка не удается при сравнении чисел с одинаковым количеством цифр, например. не удалось переставить 82 и 44.

Вот небольшой пример:

#include <bits/stdc++.h>
#include <string>
#include <algorithm>
#include <vector>

using namespace std;

string ltrim(const string &);
string rtrim(const string &);

bool comp(const string &left, const string &right)
{
    if (left.size() < right.size())
    {
        return true;
    }
    if (left.size() > right.size())
    {
        return false;
    }

    for (unsigned long int i = 0; i < left.size(); i++)
    {
        if (left[i] > (right[i]))
        {
            // left is bigger
            return false;
        }
    }
    // left is smaller or equal
    return true;
}

int main(void)
{
    vector<string> str_nums = {"82", "44", "131", "2"};

    sort(str_nums.begin(), str_nums.end(), comp);

    for (string e : str_nums)
    {
        cout << e << endl;
    }
    return 0;
}

Это выводит:

2
82
44
131

где числа с разными цифрами подходят, но числа с двумя цифрами не сортируются. В чем проблема, пожалуйста?

Используйте отладчик, чтобы понять, почему он решил, что 82 < 44.

Scott Hunter 25.06.2024 16:28

Использование правильной проекции гарантирует правильность: return std::tuple{lhs.size(), lhs} < std::tuple(rhs.size(), rhs);

Jarod42 25.06.2024 16:28

Ваша функция не сравнивает числа, она сравнивает отдельные символы. Это почти эквивалентно left <= right.

Some programmer dude 25.06.2024 16:30

В вашем цикле, когда left[i] < right[i], вы просто пропускаете эту цифру...

Jarod42 25.06.2024 16:30

Если ваши числа могут иметь ведущие 0, сравнение размеров не будет надежным.

Scott Hunter 25.06.2024 16:30

Кажется, вы просто хотите std::stoi(left) <= std::stoi(right).

Some programmer dude 25.06.2024 16:33

@Someprogrammerdude: < нет <= ;-)

Jarod42 25.06.2024 16:35

@Someprogrammerdude это 10^6 цифр, а не просто 10^6; так что это слишком долго, даже если long long unsigned stoi также выдаст ошибку, поскольку число слишком велико

YWH 25.06.2024 16:38

@YWH Тогда тебе нужно придумать другой способ сравнения чисел. Или используйте существующую библиотеку bignum. Также обратите внимание, что показанная вами функция неоднозначна. Для размера он выполняет как «меньше», так и «меньше или равно», а для символов (не чисел) — «меньше или равно». Это нарушает строгое требование слабого порядка для функций сравнения.

Some programmer dude 25.06.2024 16:40

Типичные операторы сравнения возвращают информацию о трех состояниях <, =, >

Pepijn Kramer 25.06.2024 16:43

Сначала преобразуйте строку в число, а затем сравните их (если ваши числа находятся в диапазоне 1–10^6)! Когда вы сравниваете строковый символ, сравниваются не числа, а числа ASCII. также, если вам нужно сравнить числа ascii, вы можете выполнить преобразование для этого одного символа.

Mahdy.n 25.06.2024 16:43

Если ваше задание или упражнение заключается в реализации собственных функций bignum, возможно, вам действительно захочется освежить в памяти математику в начальной и средней школе. Например, как вы выполняли обычную арифметику (сложение, деление и т. д.), используя карандаш и бумагу. Многое из этого можно более или менее точно так же перевести в компьютерную программу. Для сравнения сделайте вычитание: Результат отрицательный? Положительный? Нуль? Это скажет вам, является ли первое значение меньше, больше или равно второму значению. Так что за меньший заказ это return subtract(left, right) < 0.

Some programmer dude 25.06.2024 16:46

Комментарии Jarod42 и некоторых чуваков-программистов по поводу строгого слабого порядка дали ответ, спасибо! Проблема заключалась в том, что цифра игнорировалась, когда она была меньше.

YWH 25.06.2024 16:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
15
97
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это код:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm> // std::sort
bool compare_numstrings(std::string &left, std::string &right) {
left = left.substr(left.find_first_not_of('0'));
right = right.substr(right.find_first_not_of('0'));
if (left.size() < right.size()) {
    return true;}
else if (left.size() > right.size()) {
    return false;}
else {
    for (long int i = 0; i < left.size(); i++) {
        if (left[i] < right[i]) {
            return true;}
        else if (left[i] > right[i]) {
            return false;}}
    return true;} // return true when numbers are equal.
};
int main() {
    std::vector<std::string> v_numstrings = {"08221014", "00000013141234", "00013141235", "013141236", "008221014", "01048576", "001048575", "004414201", "04414202"};
    std::sort(v_numstrings.begin(), v_numstrings.end(), compare_numstrings);
    for (std::string numstring: v_numstrings) {
        std::cout << numstring << "\n";}
    return 0;
}

«Без использования какой-либо странной библиотеки»? std::sort находится в той же библиотеке, что и std::vector и std::string, то есть в стандартной библиотеке.

Caleth 25.06.2024 17:16

Почему вы считаете, что пузырьковой сортировки «более чем достаточно»? И почему ваш ответ в основном сосредоточен на замене той части, которая уже сработала (std::sort), а не только той части, которая не сработала (предикат)?

Sneftel 25.06.2024 17:18

Стандартная библиотека НЕ ​​странная, на самом деле НАСТОЯТЕЛЬНО рекомендуется использовать ее, когда есть возможность. Написание собственного метода сортировки, когда std::sort может выполнять только эту работу, потребует написания и тестирования большего количества кода.

Pepijn Kramer 25.06.2024 17:23

Без использования какой-либо странной библиотеки, это правильный путь. Честно говоря, этот код не пройдет проверку кода почти в каждом магазине C++ — программист, работающий над вашим кодом, скорее всего, просто выбросит его и заставит std::sort работать с ним. правильный предикат. std::sort всегда работает, если задана правильная функция предиката.

PaulMcKenzie 25.06.2024 17:30

Токсичные люди прибыли в рекордно короткие сроки, братан.

gerardo.arroyo.s 25.06.2024 17:33

Нет, мы предупреждаем других читателей, что ваш код — это не то, что написали бы опытные разработчики. Это не токсично, это похоже на то, как учителя пытаются вас чему-то научить :) Обратите внимание: вы передаете свои данные по значению в функцию сортировки, это приводит к ненужному копированию (что является проблемой для больших строк).

Pepijn Kramer 25.06.2024 17:34

Ребята, код работает как задумано, я только что добавил код для удаления возможных нулей в начале. Если вы хотите использовать сортировку, это нормально, мне это просто не нравится. важно то, что код дает точные результаты, и это действительно так.

gerardo.arroyo.s 25.06.2024 17:34

Я ценю усилия, которые вы приложили к этому. Однако, по нашему мнению, это не тот тип кодирования, который другие люди должны копировать или использовать в качестве образца.

Pepijn Kramer 25.06.2024 17:36

Это эквивалентно тому, что я захожу в раздел Python и отвечаю на вопрос, написав цикл вместо использования понимания списка или множества, и, кроме того, называю последнее «странным». Программисты Python сказали бы что-нибудь, если бы я дал в качестве ответа цикл for, точно так же, как программисты C++ говорят вам что-то о том, что вы должны написать свой собственный вид, когда std::sort существует. Это не токсично.

PaulMcKenzie 25.06.2024 17:37

Угадай, что. Я нуб. Для меня сортировка странная, я не умею ею пользоваться и мне не нужно ею пользоваться. Моя цель — заставить эту штуку работать точно, и она работает. Я знаю кое-что о Python. Понимание списков является альтернативой циклам for, у которого есть свои плюсы и минусы, и использование цикла for вместо понимания списка неплохо, если код работает так, как задумано, поэтому никто не должен даже жаловаться на это. Разве этот сайт не о том, как добиться цели? Он не просил необычного или профессионального решения. ему нужно только решение, и я его предлагаю

gerardo.arroyo.s 25.06.2024 17:44

Раздел комментариев предназначен для комментариев, и другие прокомментировали. Если вы публикуете ответ, разумно принять комментарии о том, как можно улучшить ответ или почему был использован использованный подход — это не токсично. Кроме того, когда вы публикуете ответ, он предназначен не только для исходного автора, но и для других, кто может выполнять поиск и встретить тот же вопрос StackOverflow.

PaulMcKenzie 25.06.2024 18:44

Блин, я только что научился пользоваться сортировкой, и наконец-то сделал это с помощью сортировки.

gerardo.arroyo.s 28.06.2024 17:45

Вам нужно удалить все ведущие нули. Кроме того, вы не сравниваете строки, а сравниваете «большие числа», поэтому почти всегда имеет смысл написать класс, который поможет вам поддерживать некоторые инварианты (например, отсутствие ведущих 0 и only digits).

Объединив это с оператором космического корабля С++ 20, который может сравнивать меньшее/равное/большее, вы можете сделать это следующим образом:

Живая демонстрация: https://onlinegdb.com/zLYAt2MTa

И если порядок сортировки доступен, вы можете использовать std::sort

#include <string>
#include <stdexcept>
#include <iostream>
#include <cassert>
#include <algorithm>
#include <vector>

class big_number_t
{
public:
    explicit big_number_t(const std::string_view sv)
    {
        if (sv.size() == 0ul) throw std::invalid_argument{ "input must contain at least one digit" };
        requires_all_digits(sv);
        // strip leading zeros

        auto pos = sv.find_first_not_of('0');
        if ( pos == std::string_view::npos )
        {
            m_value = "0";
        }
        else
        {
            m_value = sv.substr(pos);
        }
    }

    // Use C++20 spaceship operator if you can
    // https://en.cppreference.com/w/cpp/language/default_comparisons
    auto operator<=>(const big_number_t& rhs) const
    {
        auto result = m_value.size() <=> rhs.m_value.size();

        if (result == std::strong_ordering::equal)
        {
            for (std::size_t pos{ 0ul }; pos < m_value.size(); ++pos)
            {
                result = m_value[pos] <=> rhs.m_value[pos];
                if (result != std::strong_ordering::equal) break;
            }
        }

        return result;
    }

    bool operator==(const big_number_t& rhs) const
    {
        return m_value == rhs.m_value;
    }

    const std::string& str() const noexcept
    {
        return m_value;
    }

private:
    constexpr void requires_all_digits(const std::string_view sv)
    {
        for (const auto c : sv)
        {
            if ((c < '0') || (c > '9'))
            {
                throw std::invalid_argument{ "non digit character found" };
            }
        }
    }

    std::string m_value;
};


int main()
{
    big_number_t zero1{ "0" };
    big_number_t zero2{ "000" };
    assert(zero1 == zero2);

    big_number_t v1{ "12341349801439486140986" };
    big_number_t v2{ "0000056732859713359871" };

    big_number_t v3{ "92801439486140986" };
    big_number_t v4{ "56732859713359871" };

    big_number_t v5{ "92801439486140986" };
    big_number_t v6{ "92801439486140985" };

    big_number_t v7{ "92801439486140985" };
    big_number_t v8{ "92801439486140985" };


    assert(v1 > v2);
    assert(v3 > v4);
    assert(v5 > v6);
    assert(v7 == v8);

    std::vector<big_number_t> numbers{
        big_number_t{ "12341349801439486140986" },
        big_number_t{ "0000056732859713359871" },

        big_number_t{ "92801439486140986" },
        big_number_t{ "56732859713359871" },

        big_number_t{ "92801439486140986" },
        big_number_t{ "92801439486140985" }
    };

    std::sort(numbers.begin(), numbers.end());

    for (const auto& number : numbers)
    {
        std::cout << number.str() << "\n";
    }
}

@Jarod42 Спасибо, ты прав (я просто поторопился)

Pepijn Kramer 25.06.2024 17:24

Код исправлен для ввода всех нулей.

Pepijn Kramer 25.06.2024 17:32
Ответ принят как подходящий

Вы не возвращаете true, когда видите большую цифру в right, поэтому сравнивается более поздняя меньшая цифра и возвращается false. Н.б. вы не соответствуете требованиям для std::sort, так как возврат true при сравнении объектов недействителен, вам нужно строго меньше.

bool comp(const string &left, const string &right)
{
    if (left.size() < right.size())
    {
        return true;
    }
    if (left.size() > right.size())
    {
        return false;
    }

    for (unsigned long int i = 0; i < left.size(); i++)
    {
        if (left[i] > (right[i]))
        {
            // left is bigger
            return false;
        }
        if (left[i] < right[i])
        {
            // right is bigger
            return true;
        }
    }
    // left is equal
    return false;
}

Смотрите на колиру

Однако есть гораздо более простой способ выполнить это сравнение: составить std::tuple каждого объекта, который вы хотите сравнить, в порядке приоритета.

bool comp(const string &left, const string &right)
{
    return std::forward_as_tuple(left.size(), left) < std::forward_as_tuple(right.size(), right);
}

Другие вопросы по теме