【问题标题】:C++ comparison function for "version sort"?“版本排序”的C++比较函数?
【发布时间】:2021-12-11 06:14:28
【问题描述】:

我看到了Compare two string as numeric value,我可以查看sort 命令行实用程序的源代码。但令我惊讶的是,我没有在 Boost 或 StackOverflow 中找到对 man sort 调用 --version-sort 的通用字符串比较的参考实现——使用嵌入数字对字符串进行排序。我知道如果我们想使用符号、分隔符和小数进行完整的数字排序,会存在语言环境问题,但版本排序似乎更容易(并且解决了非零填充的常见情况下的顺序文件名)。有这样的通用实现吗?

如果没有, 一个只支持无符号整数的简单通用实现似乎是

  1. 两者都为空 => 相等。
  2. 找出每个范围内的第一个数字。
  3. std::lexicographical_compare_three_way 对应的领先范围。
  4. 如果它们不相等,我们就完成了。
  5. 如果它们相等,则找到数字跨度的末尾。
  6. 用数字比较对应的数字跨度。
  7. 递归比较其余范围。

数字跨度部分类似于

#include <algorithm>
#include <cassert>
#include <cctype> // For std::isdigit
#include <string_view>


struct is_digit_t {
    // See https://en.cppreference.com/w/cpp/string/byte/isdigit
    [[nodiscard]] bool operator()(unsigned char c) const {
         return std::isdigit(c);
    }
    [[nodiscard]] bool operator()(const std::integral auto& c) const {
        if (c != static_cast<std::decay_t<decltype(c)>>(static_cast<unsigned char>(c))) {
            return false; // Has to be itself as an unsigned char.
        }
        return (*this)(static_cast<unsigned char>(c));
    }
};

template <typename It0, typename It1, typename IsDigit = is_digit_t>
std::strong_ordering compare_strings_containing_only_digits_three_way(
    It0 b0, It0 e0, 
    It1 b1, It1 e1,
    [[maybe_unused]] IsDigit is_digit = {})
{
    assert(std::all_of(b0, e0, is_digit));
    assert(std::all_of(b1, e1, is_digit));    
    // Skip leading zeros:
    auto nonzero = [](const auto& c) { return c != '0'; };
    const auto bnz0 = std::find_if_not(b0, e0, nonzero);
    const auto bnz1 = std::find_if_not(b1, e1, nonzero);
    const auto s0 = std::distance(bnz0, e0);
    const auto s1 = std::distance(bnz1, e1);
    if (s0 != s1) {
        return s0 < s1 ? std::strong_ordering::less : std::strong_ordering::greater;
    }
    // Same number of digits => lexicographical compare is numerical compare:
    const auto numerical = std::lexicographical_compare_three_way(b0, e0, b1, e1);
    if (numerical != 0) {
        return numerical;
    }
    // Tiebreaker: "1" < "01" < "001" < "2":
    return std::lexicographical_compare_three_way(b0, bnz0, b1, bnz1);
}

template <typename It0, typename It1, typename IsDigit = is_digit_t>
std::strong_ordering compare_strings_containing_unsigned_integers_three_way(
    It0 b0, It0 e0, 
    It1 b1, It1 e1,
    IsDigit is_digit = {}) 
{
    if (b0 == e0 && b1 == e1) { // Totally empty => equal.
        return std::strong_ordering::equal;
    }
    const auto numStart0 = std::find_if(b0, e0, is_digit);
    const auto numStart1 = std::find_if(b1, e1, is_digit);

    
    if (const auto leadingCmp = std::lexicographical_compare_three_way(b0, numStart0, b1, numStart1);
        leadingCmp != std::strong_ordering::equal) {
        return leadingCmp; // Don't have to worry about the numbers.
    }
    const auto numEnd0 = std::find_if_not(numStart0, e0, is_digit);
    const auto numEnd1 = std::find_if_not(numStart1, e1, is_digit);
    if (auto numberCmp = compare_strings_containing_only_digits_three_way(numStart0, numEnd0, numStart1, numEnd1, is_digit); 
        numberCmp != std::strong_ordering::equal) {
        return numberCmp; // Number-part tie-broke it.
    }
    // Recursively deal with everything after the first matched numbers:
    return compare_strings_containing_unsigned_integers_three_way(numEnd0, e0, numEnd1, e1);
}

https://godbolt.org/z/eEKbMj9vj

所以...

  • 这看起来对吗?
  • 有更清洁的方法吗?
  • 真的没有 Boost 算法可以解决这个问题吗?

【问题讨论】:

  • 我会分两步进行:提取-然后-比较,使用类似:std::vector&lt;int&gt; get_numbers(std::string_view)
  • 这需要一个不必要的分配。
  • 工作代码应该转到codereview.stackexchange.com

标签: c++ sorting string-comparison


【解决方案1】:

std::sort 需要 O(N·log(N)) 比较。将其与 O(N) 将每个字符串单独转换为可以通过内置 &lt; 进行比较的数字进行比较。

您应该考虑填充std::vector&lt; std::pair&lt;int, std::string&gt;&gt; 并通过简单的std::sort(v.begin(),v.end()) 对其进行排序,最终提取原始字符串以获得所需的输出,而不是将转换嵌入到比较中。

【讨论】:

  • 这种方法需要 O(N) 额外的分配,以优化字符串中存在影响排序的数字的情况。此外,其他算法需要排序,例如std::min_element
  • @Ben 我不会因为额外的空间而忽视这个建议,除非您在内存受限的环境中工作或有大量N 的订单。如果您为排序进行就地解析,那么您正在执行此计算N*log(N)times,这与执行一次转换相比效率非常低——即使有额外存储的成本。存储大小将是固定的,并且预先知道大小。即使分配成本确实令人担忧,多态分配器也可以提供帮助(尽管确实是先配置文件)
猜你喜欢
  • 2012-05-27
  • 2016-02-06
  • 1970-01-01
  • 1970-01-01
  • 2020-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多