CaseyCarter / cmcstl2

An implementation of C++ Extensions for Ranges
Other
222 stars 68 forks source link

Help with adapting split_view to take a length rather than a pattern. #277

Open melton1968 opened 5 years ago

melton1968 commented 5 years ago

I initially tried to get this functionality by combining off-the-shelf views, but I don't think that is possible.

So, I am trying to write a group_view that is analogous to split_view but takes a length rather than a pattern. After puzzling over the code for a long while, here is how I think I should proceed:

  1. Change all the construction paths to take a Length rather than a Pattern and store the Length in the view . The Length will necessarily be an Integral type. If the length

  2. Change outer_iterator::operator++ to move forward Length units at a time.

  3. Change outer_iterator::value_type to be something like subrange. Or, can I use subrange directly here?

Is there a better approach?

Any guidance is appreciated.

cheers, mark

CaseyCarter commented 5 years ago

It may be helpful to look at the implementation of range-v3's chunk_view, which splits a range into a range of subranges of a runtime-specified size.

melton1968 commented 5 years ago

So I have group_n (produces a range of ranges with each subrange containing n elements where n is given by a range) working for a simple test:

Note 1. v::chr::alpha() produces lowercase letters Note 2. v::in_range(3, 7) produces integers in the open range (3,7)

auto generator = v::chr::alpha() | v::group_n(v::in_range(3, 7)) | v::take(20);
for (auto values : generator)
{
    for (auto value : values)
        cout << value;
     cout << Lendl;
}

produces:

icoheo
yzzzesz
ycmuuhd
kcx
...

A couple of questions:

  1. Is there any interest, once I have this cleaned up and tested, in taking this as a contribution?

  2. I am a little unsure about the following snippets of code from the implementation of outer_iterator (which follows very closely to split_view::outer_iterator with pattern replaced by length). Am I manipulating the iterators correctly in operator++?

    using subrange_iter_t = counted_iterator<iterator_t<Base>>;
    using value_type = subrange<subrange_iter_t, default_sentinel, subrange_kind::sized>;

    constexpr value_type operator*() const
    {
        auto iter_n = counted_iterator(current(), *parent_->length_.begin());
        auto rng = subrange{ iter_n, default_sentinel{} };
        return rng;
    }

    constexpr outer_iterator& operator++() {
        auto& cur = current();
        const auto end = __stl2::end(parent_->base_);

        index_t n = *parent_->length_.begin();
        ++(parent_->length_.begin());
        while (++cur != end and n > 0)
            --n;

        return *this;
    }

After this, I plan on implementing group which will be analogous to group_n but taking a predicate indicating where to break the groups rather than a count.

cheers, mark

melton1968 commented 5 years ago

This is my implementation of group_n. It doesn't include any optimizations for forward_iterator, but otherwise is functional.

Example usage:

TEST(View, GroupNFixed)
{
    strings expected = { "abcd", "efgh", "ijkl", "mnop", "qrst",
                         "uvwx", "yzab", "cdef", "ghij", "klmn" };
    auto actual = v::iota(0)
        | v::transform([](auto i) { return char((i % 26) + 'a'); })
        | v::group_n(4)
        | v::take(10)
        | v::subrange_to<string>
        | r::to<vector>;
    EXPECT_EQ(actual, expected);
};
#pragma once
#include <stl2/type_traits.hpp>
#include <stl2/detail/fwd.hpp>
#include <stl2/detail/algorithm/mismatch.hpp>
#include <stl2/detail/concepts/object.hpp>
#include <stl2/detail/iterator/default_sentinel.hpp>
#include <stl2/detail/range/access.hpp>
#include <stl2/detail/range/concepts.hpp>
#include <stl2/detail/view/view_closure.hpp>
#include <stl2/view/all.hpp>
#include <stl2/view/single.hpp>
#include "core/common.h"

namespace std::experimental::ranges {

namespace detail::group_n {

template<class Range, class Count>
constexpr bool ViewReqs = View<Range>
    && View<Count>
    && Integral<iter_value_t<iterator_t<Count>>>;

template<InputRange Range, InputRange Count>
requires ViewReqs<Range, Count>
struct base
{
private:
    using subrange_iter_t = counted_iterator<iterator_t<Range>>;
    using value_type = subrange<subrange_iter_t, default_sentinel, subrange_kind::sized>;

    sentinel_t<Range> end_range_ {};
    iterator_t<Range> current_range_ {};
    iterator_t<Range> current_group_ {};
    iterator_t<Count> current_count_ {};
    index_t actual_count_ {};

protected:
    void reset_iterators(Range& range, Count& count)
    {
    end_range_ = __stl2::end(range);
    current_range_ = __stl2::begin(range);
    current_group_ = __stl2::begin(range);
    current_count_ = __stl2::begin(count);
    actual_count_ = 0;
    }

    auto end_range() const
    { return end_range_; }

    auto current_group() const
    { return current_group_; }

    value_type group_subrange() const
    {
    auto iter_n = counted_iterator{ current_group_, int(actual_count_) };
    return subrange{ iter_n, default_sentinel{} };
    }

    void advance_group() 
    { 
    current_group_ = current_range_;
    index_t max_count = *current_count_;
    actual_count_ = 0;
    while (current_range_ != end_range_ and max_count > 0)
    {
        ++current_range_;
        ++actual_count_;
        --max_count;
    }
    }

    void advance_count()
    {
    auto iter = current_count_;
    ++iter;
    }
};

}; // end ns detail::group_n

template<InputRange Range, InputRange Count> requires detail::group_n::ViewReqs<Range, Count>
struct group_n_view : public detail::group_n::base<Range, Count>
{
private:
    using Base = detail::group_n::base<Range, Count>;
    using Base::reset_iterators;
    using group_value_type = typename Base::value_type;
    struct group_iterator;

    Range range_ {};
    Count count_ {};

public:
    group_n_view() = default;

    constexpr group_n_view(Range range, Count count)
    : range_(std::move(range))
    , count_(std::move(count))
    {}

    constexpr group_n_view(Range range, Integral count)
    requires Constructible<Count, single_view<int>>
        : range_(std::move(range))
        , count_(std::move(single_view{count}))
    {}

    constexpr auto begin()
    {
    reset_iterators(range_, count_);
    return group_iterator{ *this };
    }

    constexpr auto end() const { return default_sentinel{}; }
};

template<class Range, class Count>
group_n_view(Range&&, Count&&) -> group_n_view<all_view<Range>, all_view<Count>>;

template<class Range, Integral Count>
group_n_view(Range&&, Count) -> group_n_view<all_view<Range>, single_view<Count>>;

template<InputRange Range, InputRange Count>
requires detail::group_n::ViewReqs<Range, Count>
struct group_n_view<Range, Count>::group_iterator
{
private:
    using Parent = group_n_view;
    Parent *parent_{ nullptr };

public:
    using iterator_category = __stl2::input_iterator_tag;
    using difference_type = iter_difference_t<iterator_t<Range>>;
    using value_type = group_n_view::group_value_type;

    group_iterator() = default;

    constexpr explicit  group_iterator(Parent& parent)
    : parent_(std::addressof(parent))
    { parent_->advance_group(); }

    constexpr auto operator*() const
    { return parent_->group_subrange(); }

    constexpr group_iterator& operator++()
    {
    parent_->advance_group();
    parent_->advance_count();
    return *this;
    }

    constexpr decltype(auto) operator++(int)
    { ++*this; }

    friend constexpr bool operator==(const group_iterator& x, default_sentinel)
    { return x.parent_->current_group() == x.parent_->end_range(); }

    friend constexpr bool operator==(default_sentinel x, const group_iterator& y)
    { return y == x; }

    friend constexpr bool operator!=(const group_iterator& x, default_sentinel y)
    { return !(x == y); }

    friend constexpr bool operator!=(default_sentinel x, const group_iterator& y)
    { return !(y == x); }
};

namespace view {
struct __group_n_fn {
    template<class E, class F>
    constexpr auto operator()(E&& e, F&& f) const
    STL2_REQUIRES_RETURN(group_n_view{static_cast<E&&>(e), static_cast<F&&>(f)});

    template<CopyConstructible T>
    constexpr auto operator()(T&& t) const
    { return detail::view_closure{*this, std::forward<T>(t)}; }
};

inline constexpr __group_n_fn group_n {};
}; // end ns view

}; // end ns stl2