可以使用 C++20 范围就地完成字符串的 trim 吗?
Can trim of a string be done inplace with C++20 ranges?
受 trim 的可爱 cppreference example 和 C++20 的启发,我编写了以下代码(我已将 return 类型更改为 void
和 arg std::string&
因为我的“问题”(我正在发明问题来学习 C++20)在使用 std::string_view
arg 和 returns std::string
的原始代码中不存在) .
void trim(std::string& in)
{
auto view
= std::views::all(in)
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
std::string result{view.begin(), view.end()};
in = std::move(result);
}
这里的问题是这不是原地,这意味着创建了新字符串。
我可以编写更丑陋的代码来就地执行此操作,而且我知道传统的 C++ 算法不知道容器的存在,但我想知道 C++20 是否有一些技巧可以让我以优雅的方式执行 trim,但是也到位了。
这也是我的丑陋原地 trim(不确定它是否工作正常,但想法是它确实 trim 原地):
void trim2(std::string& s) {
// ugly and error prone, but inplace
const auto it1 = std::ranges::find_if_not(s, isspace);
const auto it2 = std::ranges::find_if_not(s.rbegin(), s.rend(), isspace);
const size_t shift = (it1==s.end()) ? 0: std::distance(s.begin(), it1);
const size_t result_size = s.size() - shift - ((it2==s.rend()) ? 0 : std::distance(s.rbegin(), it2));
std::shift_left(s.begin(), s.end(), shift);
s.resize(result_size);
}
编辑:最初这个问题声称 in.assign
是 UB,但 T.C。纠正我。但根据我对 C++23 草稿分配的理解,仍会导致创建临时字符串。
是的,有可能:
void trim(std::string& s) {
auto view = s
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
auto [in, out] = std::ranges::copy(view, s.begin());
s.erase(out, s.end());
}
demo.
也许是这样的?
void trim(std::string& s) {
auto not_space = [](unsigned char c){ return !std::isspace(c); };
// erase the the spaces at the back first
// so we don't have to do extra work
s.erase(
std::ranges::find_if(s | std::views::reverse, not_space).base(),
s.end());
// erase the spaces at the front
s.erase(
s.begin(),
std::ranges::find_if(s, not_space));
}
assign
与任意迭代器需要创建一个临时的(因为改变字符串可以以任意方式影响迭代器的结果,并且因为抛出迭代器操作必须保持原始字符串不变),但是每个主要实现在给定字符串自己的迭代器时做正确的事情。
所以我们可以展开 reverse_iterator
-ness 的两个级别:
auto view
= in
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
in.assign(view.begin().base().base(), view.end().base().base());
(不过我会亲自去听 Barry 的回答。)
此外,很少需要显式使用 views::all
;适配器会自动执行。
受 trim 的可爱 cppreference example 和 C++20 的启发,我编写了以下代码(我已将 return 类型更改为 void
和 arg std::string&
因为我的“问题”(我正在发明问题来学习 C++20)在使用 std::string_view
arg 和 returns std::string
的原始代码中不存在) .
void trim(std::string& in)
{
auto view
= std::views::all(in)
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
std::string result{view.begin(), view.end()};
in = std::move(result);
}
这里的问题是这不是原地,这意味着创建了新字符串。 我可以编写更丑陋的代码来就地执行此操作,而且我知道传统的 C++ 算法不知道容器的存在,但我想知道 C++20 是否有一些技巧可以让我以优雅的方式执行 trim,但是也到位了。
这也是我的丑陋原地 trim(不确定它是否工作正常,但想法是它确实 trim 原地):
void trim2(std::string& s) {
// ugly and error prone, but inplace
const auto it1 = std::ranges::find_if_not(s, isspace);
const auto it2 = std::ranges::find_if_not(s.rbegin(), s.rend(), isspace);
const size_t shift = (it1==s.end()) ? 0: std::distance(s.begin(), it1);
const size_t result_size = s.size() - shift - ((it2==s.rend()) ? 0 : std::distance(s.rbegin(), it2));
std::shift_left(s.begin(), s.end(), shift);
s.resize(result_size);
}
编辑:最初这个问题声称 in.assign
是 UB,但 T.C。纠正我。但根据我对 C++23 草稿分配的理解,仍会导致创建临时字符串。
是的,有可能:
void trim(std::string& s) {
auto view = s
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
auto [in, out] = std::ranges::copy(view, s.begin());
s.erase(out, s.end());
}
demo.
也许是这样的?
void trim(std::string& s) {
auto not_space = [](unsigned char c){ return !std::isspace(c); };
// erase the the spaces at the back first
// so we don't have to do extra work
s.erase(
std::ranges::find_if(s | std::views::reverse, not_space).base(),
s.end());
// erase the spaces at the front
s.erase(
s.begin(),
std::ranges::find_if(s, not_space));
}
assign
与任意迭代器需要创建一个临时的(因为改变字符串可以以任意方式影响迭代器的结果,并且因为抛出迭代器操作必须保持原始字符串不变),但是每个主要实现在给定字符串自己的迭代器时做正确的事情。
所以我们可以展开 reverse_iterator
-ness 的两个级别:
auto view
= in
| std::views::drop_while(isspace)
| std::views::reverse
| std::views::drop_while(isspace)
| std::views::reverse;
in.assign(view.begin().base().base(), view.end().base().base());
(不过我会亲自去听 Barry 的回答。)
此外,很少需要显式使用 views::all
;适配器会自动执行。