11

Given an input sequence, the standard algorithms std::count and std::accumulate count the number of occurances of a specific value (or predicate matches for std::count_if) and the accumulation of a given associative operation (sum, product, Boolean or/and, min/max, string concatenation, etc.), respectively.

What if one wants to know whether an input sequence contains exactly/at least/at most n occurances/matches, or accumulates to a sum of exactly/at least/at most n? The brute-force way would be to compare the result of std::count or std::accumulate against the target n, but that would miss out on an early exit opportunity when the count or accumulation exceeds the target already halfway through the input sequence.

One could e.g. make a count_until as

template<class InputIt, class T, class Pred>
auto count_until(InputIt first, InputIt last, const T& value, Pred pred)
{
    auto res = 0;
    for (; first != last; ++first)
        if (*first == value && pred(++res))
            break; // early exit if predicate is satisfied
    return std::make_pair(first, res); // iterator and value to allow continuation    
}

and from which one could test for equality/at least/at most by using a suitable predicate and comparison against the returned count.

Questions:

  • is it possible to write count_until (and similarly for accumulate_until) using a combination existing standard algorithms, possibly in combination with a suitable Boost.Iterator?
  • In particular, I was thinking of a find_if over an accumulate_iterator where the predicate would extract the count or sum from the iterator.
  • Or do count_until and accumulate_until warrant inclusion as standalone primitives in a future version of the Standard Library?

Edit: I think the most useful formulation is to return a std::pair of an iterator and the count at the point where the predicate is first satisfied. This enables users to continue iterating.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • you can use an `std::map` to keep the count of the occurrences – user2485710 Dec 26 '13 at 11:00
  • 1
    You could already do what you want with `std::find_if` with a Predicate with state. – Jarod42 Dec 26 '13 at 11:10
  • @Jarod42 care to formulate an answer? Preferably where the predicate is passed from the outside to `count_until`. – TemplateRex Dec 26 '13 at 11:45
  • 1
    @TemplateRex: my version posted as answer. – Jarod42 Dec 26 '13 at 11:58
  • By the way, the names appears to me ambiguous as the 'until' predicate may refer to Iterator or 'working result'. – Jarod42 Dec 26 '13 at 12:20
  • @Jarod42 the `until` applies to a satisfied predicate. Every algorithm takes two iterators as an input range, and none of them has the `until` suffix, so I am not really worried about such confusion. – TemplateRex Dec 26 '13 at 12:38
  • @TimothyShields no, "exactly `N`" and "at most `N`" can be aborted after the `N+1`-th encounter, "at least `N`" after the `N-th` one. – TemplateRex Apr 21 '14 at 17:46

1 Answers1

7

I was thinking of a combination of std::find_if with a state predicate: (Pred is normal user predicate.)

template<class InputIt, class T, class Pred>
typename iterator_traits<InputIterator>::difference_type
count_until(InputIt begin, InputIt end, const T& value, Pred pred)
{
    typename iterator_traits<InputIterator>::difference_type count = 0;
    auto internal_pred = [&count, &value, &pred](decltype(*begin) elem) {
        return elem == value && pred(++count);
    };

    std::find_if(begin, end, internal_pred);
    return count;
}

template<class InputIt, class T, class Pred>
T accumulate_until(InputIt begin, InputIt end, T value, Pred pred)
{
    auto internal_pred = [&value, &pred] (const T& t) {
        value += t;
        return pred(value);
    };
    std::find_if(begin, end, internal_pred);
    return value;
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Maybe use `std::iterator_traits::difference_type` instead of `size_t`. – Stephan Dollberg Dec 26 '13 at 11:57
  • 1
    Thanks and accepted. BTW, the `accumulate_until` only works for monotonous accumulation (say addition for positive integers). If the input can be negative, then `accumulate_until` will stop as soon as the total becomes negative (e.g. in gambler's ruin problem). So I think that `accumulate_until` should return a `std::pair` of an iterator and a value, rather than only a value, so that users can decide to continue iterating from the point of first failure. I also considered returning only an iterator from `count_until`, but it seems impossible to implement `is_count_equal_to` that way. – TemplateRex Dec 26 '13 at 13:12
  • Both methods may return `pair` (easily). `is_count_equal_to(.., n)` may be computed as `n == count_until(.., MoreThan(n + 1))`. – Jarod42 Dec 26 '13 at 20:30
  • That short-circuiting logic in count_until's internal predicate is a work of art. – mskfisher Jun 22 '16 at 12:36