【发布时间】:2021-12-11 06:14:28
【问题描述】:
我看到了Compare two string as numeric value,我可以查看sort 命令行实用程序的源代码。但令我惊讶的是,我没有在 Boost 或 StackOverflow 中找到对 man sort 调用 --version-sort 的通用字符串比较的参考实现——使用嵌入数字对字符串进行排序。我知道如果我们想使用符号、分隔符和小数进行完整的数字排序,会存在语言环境问题,但版本排序似乎更容易(并且解决了非零填充的常见情况下的顺序文件名)。有这样的通用实现吗?
如果没有, 一个只支持无符号整数的简单通用实现似乎是
- 两者都为空 => 相等。
- 找出每个范围内的第一个数字。
-
std::lexicographical_compare_three_way对应的领先范围。 - 如果它们不相等,我们就完成了。
- 如果它们相等,则找到数字跨度的末尾。
- 用数字比较对应的数字跨度。
- 递归比较其余范围。
数字跨度部分类似于
#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<int> get_numbers(std::string_view)。 -
这需要一个不必要的分配。
-
工作代码应该转到codereview.stackexchange.com
标签: c++ sorting string-comparison