什么是使用C++ 20范围删除最后一个元素的最佳方法

发布于 2025-01-18 00:33:12 字数 453 浏览 6 评论 0原文

有没有比反转两次更好的方法来使用 c++20 范围删除容器中的最后一个元素?

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::reverse 
                            | std::ranges::views::drop(1) 
                            | std::ranges::views::reverse)
    {
        std::cout << d << std::endl;
    }
}

Is there any better way to drop last element in container using c++20 ranges than reverse it twice?

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::reverse 
                            | std::ranges::views::drop(1) 
                            | std::ranges::views::reverse)
    {
        std::cout << d << std::endl;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

我喜欢麦丽素 2025-01-25 00:33:12

您需要的是views :: drop_last来自 p2214 ,并具有第2层的优先级。

作为纸说

我们将通过这个家庭的其他潜在范围适配器,
讨论如何根据现有适配器实施它们:

  • take_last(n)drop_last(n)视图:: take_last(n)等同于
    视图::反向|视图::带(n)|视图::反向。但这有点
    昂贵,尤其是对于非共同观点。用于随机访问,大小
    范围,我们可能想要r |视图:: take_last(n)评估为r |视图:: drop(r.size() - n),而这种愿望确实是这样的症结
    整个问题 - 等效版本足够好,还是我们应该
    想要做对吗?

由于vector是一个随机访问,大小的范围,您只能做

for (const auto& d: foo | std::views::take(foo.size() - 1))
{
    std::cout << d << std::endl;
}

What you need is views::drop_last which comes from p2214 and has a priority of Tier 2.

As the paper says:

We’ll go through the other potential range adapters in this family and
discuss how they could be implemented in terms of existing adapters:

  • take_last(N) and drop_last(N). views::take_last(N) is equivalent to
    views::reverse | views::take(N) | views::reverse. But this is somewhat
    expensive, especially for non-common views. For random-access, sized
    ranges, we’re probably want r | views::take_last(N) to evaluate as r | views::drop(r.size() - N), and that desire is really the crux of this
    whole question — is the equivalent version good enough or should we
    want to do it right?

Since vector is a random-access, sized range, you can just do

for (const auto& d: foo | std::views::take(foo.size() - 1))
{
    std::cout << d << std::endl;
}
假面具 2025-01-25 00:33:12

使用span怎么样?

#include <iostream>
#include <span>
#include <vector>

int main() {
  std::vector<int> foo{1, 2, 3, 4, 5, 6};

  for (const auto& d : std::span(foo.begin(), foo.end() - 1)) {
    std::cout << d << '\n';
  }
}

How about using span?

#include <iostream>
#include <span>
#include <vector>

int main() {
  std::vector<int> foo{1, 2, 3, 4, 5, 6};

  for (const auto& d : std::span(foo.begin(), foo.end() - 1)) {
    std::cout << d << '\n';
  }
}
太阳哥哥 2025-01-25 00:33:12

您可以通过以下方式进行良好的近似:

struct drop_last_t {
    template <std::ranges::sized_range R>
        requires std::ranges::viewable_range<R>
    friend auto operator|(R&& r, drop_last_t) {
        auto n = std::ranges::size(r);
        return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
    }
};
inline constexpr drop_last_t drop_last;

让您

for (const auto& d: foo | drop_last)

这不是一个完美的范围适配器,因为你不能写像 auto my_adaptor = transform(f) | 这样的东西drop_last; 为此,您需要 P2387,它是一个 C++23 库特征。在 C++23 中,您可以这样编写:

struct drop_last_t : std::ranges::range_adaptor_closure<drop_last_t>
{
    template <std::ranges::sized_range R>
        requires std::ranges::viewable_range<R>
    auto operator()(R&& r) const {
        auto n = std::ranges::size(r);
        return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
    }
};
inline constexpr drop_last_t drop_last;    

现在这是一个功能齐全的范围适配器。当前的 libstdc++ 特定版本看起来像这样(只是为了演示,实际上并不这样做 - 这这不是在 C++23 中执行此操作的方式)。


当然,这仅限于尺寸范围。这可能有各种各样的方向。您可以通过执行 std::ranges::distance(r) 来支持任何前向范围(以多次遍历为代价)。但定制实施可以做得更好。对于 bidi+common,您只需停在 prev(end(r)) 处。仅对于前向,您可以一次前进两个迭代器,等等。只是需要考虑的事情。

You can do a good approximation via:

struct drop_last_t {
    template <std::ranges::sized_range R>
        requires std::ranges::viewable_range<R>
    friend auto operator|(R&& r, drop_last_t) {
        auto n = std::ranges::size(r);
        return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
    }
};
inline constexpr drop_last_t drop_last;

That lets you:

for (const auto& d: foo | drop_last)

This isn't a perfect range adaptor, since you can't write something like auto my_adaptor = transform(f) | drop_last; In order to do that, you need P2387, which is a C++23 library feature. In C++23, you'd write it this way:

struct drop_last_t : std::ranges::range_adaptor_closure<drop_last_t>
{
    template <std::ranges::sized_range R>
        requires std::ranges::viewable_range<R>
    auto operator()(R&& r) const {
        auto n = std::ranges::size(r);
        return std::views::take(std::forward<R>(r), n > 0 ? n - 1 : 0);
    }
};
inline constexpr drop_last_t drop_last;    

And now this is a completely functional range adaptor. The current libstdc++-specific version looks like this (just to demonstrate, don't actually do this - this isn't how you'd do this in C++23).


Of course, this is limited to sized ranges. There's all sorts of directions this could go. You could support any forward range by doing std::ranges::distance(r) instead (at the cost of multiple traversal). But a bespoke implementation could do better. For bidi+common, you just need to stop at prev(end(r)). For forward only, you could advance two iterators at a time, etc. Just something to think about.

烟花易冷人易散 2025-01-25 00:33:12

您可以为此使用views::slide(2)。或视图::相邻<2>如果您更喜欢元组而不是范围。

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::slide(2) 
                            | std::ranges::views::transform([](auto rg){return rg[0];}))
    {
        std::cout << d << std::endl;
    }
}

输入图片这里的描述

如果你想删除两端,你可以使用views::slide(3)并从每个窗口中取出中间部分。

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::slide(3) 
                            | std::ranges::views::transform([](auto rg){return rg[1];}))
    {
        std::cout << d << std::endl;
    }
}

您可以将这些滑动窗口视为金字塔:
输入图片这里的描述

每个十字代表金字塔的顶部,并且可以被视为金字塔底部元素的窗口。

You can use views::slide(2) for this. Or views::adjacent<2> if you prefer tuples to ranges.

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::slide(2) 
                            | std::ranges::views::transform([](auto rg){return rg[0];}))
    {
        std::cout << d << std::endl;
    }
}

enter image description here

If you wanted to remove both ends, you could use views::slide(3) and take the middle from each window.

#include <iostream>
#include <vector>
#include <ranges>

int main()
{
    std::vector<int> foo{1, 2, 3, 4, 5, 6};

    for (const auto& d: foo | std::ranges::views::slide(3) 
                            | std::ranges::views::transform([](auto rg){return rg[1];}))
    {
        std::cout << d << std::endl;
    }
}

enter image description here

You can see these sliding windows as a pyramid:
enter image description here

each cross represents the top of a pyramid and can be seen as a window on the elements at the base of the pyramid.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文