7

I attempting to use the Ranges-V3 library to slice up an container of values into a range of ranges such that neighboring ranges share boundary elements.

Consider the following:

using namespace ranges;

std::vector<int> v = { 1, 2, 3, 0, 4, 0, 5, 0, 6, 7, 8, 0, 0, 9 };
auto myRanges = v | /* something like adjacent split */
for_each( myRanges, []( auto&& range ){ std::cout << range << std::endl;} );

I would like to divide the range into overlapping subranges based whether the region fullfills two criteria:

  1. whether the element has a value of zero
  2. or is adjacent to one or more elements with a value of zero

Desired output:

[1,2,3]
[3,0,4,0,5,0,6]
[6,7,8]
[8,0,0,9]

my attempt:

auto degenerate =
  []( auto&& arg ){
    return distance( arg ) < 2;  
  };

auto myRanges = v | view::split(0) | view::remove_if( degenerate );
for_each( myRanges, []( auto&& range ){ std::cout << range << std::endl;} );

Output:

[1,2,3]
[6,7,8]

I'm at a loss on how I might

  1. "insert" the range from 3 to 6
  2. "append" the range from 8 to 9
ildjarn
  • 62,044
  • 9
  • 127
  • 211
apmccartney
  • 743
  • 8
  • 16
  • why are you passing moving reference? it will corrupt your data – Sugar Mar 01 '17 at 07:22
  • 3
    @Sugar I assume you're referring to the use of `auto&&` and of the impression that `&&` imply an rvalue reference. In this case, that sigil doesn't imply rvalue reference, but instead what Scott Meyer's calls universal references. See more (here)[https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers] – apmccartney Mar 01 '17 at 14:35
  • 2
    [Here](https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers) is a working version of apmccartney's link. – Justin Time - Reinstate Monica Mar 01 '17 at 21:34

1 Answers1

4

If I understand your requirements correctly, you can implement a generator in terms of adjacent_find:

namespace detail {
    template<typename IterT, typename SentT>
    struct seg_gen_fn {
        IterT it_;
        SentT end_;
        bool parity_ = true;

        ranges::iterator_range<IterT> operator ()() {
            if (it_ == end_) {
                return {it_, it_};
            }

            auto n = ranges::adjacent_find(
                it_, end_,
                [p = std::exchange(parity_, !parity_)](auto const a, auto const b) {
                    return a && !b == p;
                }
            );
            return {
                std::exchange(it_, n),
                n != end_ ? ranges::next(std::move(n)) : std::move(n)
            };
        }
    };

    template<typename RngT>
    constexpr auto seg_gen(RngT&& rng)
     -> seg_gen_fn<ranges::iterator_t<RngT>, ranges::sentinel_t<RngT>>
    { return {ranges::begin(rng), ranges::end(rng)}; }
} // namespace detail

auto const segmented_view = [](auto&& rng) {
    return ranges::view::generate(detail::seg_gen(decltype(rng)(rng)))
         | ranges::view::take_while([](auto const& seg) { return !seg.empty(); });
};

int main() {
    auto const ns = {1, 2, 3, 0, 4, 0, 5, 0, 6, 7, 8, 0, 0, 9};
    ranges::copy(segmented_view(ns), ranges::ostream_iterator<>{std::cout, "\n"});
}

Online Demo
Not exactly as succinct as one might hope... :-[


That's probably fine for one-off code, but a little more work and it can be a lot more reusable:

namespace detail {
    namespace tag = ranges::tag;

    template<
        typename RngT, typename PredT, typename IterT = ranges::iterator_t<RngT>,
        typename StateT = ranges::tagged_compressed_tuple<
            tag::begin(IterT), tag::end(ranges::sentinel_t<RngT>),
            tag::current(bool), tag::fun(ranges::semiregular_t<PredT>)
        >
    >
    struct seg_gen_fn : private StateT {
        constexpr seg_gen_fn(RngT&& rng, PredT pred)
          : StateT{ranges::begin(rng), ranges::end(rng), true, std::move(pred)}
        { }

        ranges::iterator_range<IterT> operator ()() {
            StateT& state = *this;
            auto& it = state.begin();
            if (it == state.end()) {
                return {it, it};
            }

            auto& parity = state.current();
            auto n = ranges::adjacent_find(
                it, state.end(),
                [p = std::exchange(parity, !parity), &pred = state.fun()]
                (auto const& a, auto const& b) {
                    return !pred(a) && pred(b) == p;
                }
            );
            return {
                std::exchange(it, n),
                n != state.end() ? ranges::next(std::move(n)) : std::move(n)
            };
        }
    };

    template<typename RngT, typename PredT>
    constexpr seg_gen_fn<RngT, PredT> seg_gen(RngT&& rng, PredT pred) {
        return {std::forward<RngT>(rng), std::move(pred)};
    }
} // namespace detail

auto const segmented_view = [](auto&& rng, auto pred) {
    return ranges::view::generate(detail::seg_gen(decltype(rng)(rng), std::move(pred)))
         | ranges::view::take_while([](auto const& seg) { return !seg.empty(); });
};

int main() {
    auto const ns = {1, 2, 3, 0, 4, 0, 5, 0, 6, 7, 8, 0, 0, 9};
    ranges::copy(
        segmented_view(ns, [](auto const n) { return n == 0; }),
        ranges::ostream_iterator<>{std::cout, "\n"}
    );
}

Online Demo

Concept checking and projections are left as an exercise.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • @ildjam Wow! Thank you very much for the reusable version in your edit. – apmccartney Apr 21 '18 at 17:19
  • @apmccartney : You're welcome. :-] I revisited the answer after a random upvote and felt compelled to update it for being too specific. I'm not sure it handles leading zeroes as desired, but putting them into their own segment only involves changing the hardcoded `true` for parity (or deferring initialization of parity until the first call). – ildjarn Apr 22 '18 at 03:54