Преобразование нескольких элементов итератора - PullRequest
0 голосов
/ 01 ноября 2009

Моя проблема более сложна, чем эта, поэтому я сузил ее до очень простого примера, который показал бы мне достаточно, чтобы уметь справляться с остальными.

Скажем, у меня есть входной итератор. Я хочу создать новый итератор ввода, полученный из него, где каждый элемент представляет собой комбинацию нескольких последовательных элементов исходного ввода со следующим шаблоном. Длина серии кодируется во входной последовательности.

Входной сигнал: { 1 1 2 3 4 4 6 7 8 9 ... }

Выход: { (1) (3+4) (6+7+8+9) ... }

Я думал, что такая функция может обрабатывать отдельный элемент и увеличивать входной итератор начала (передаваемый по ссылке). В моих комментариях есть несколько вопросов, и я хотел бы знать, есть ли хороший способ сделать это для всего потока элементов.

РЕДАКТИРОВАТЬ: Я знаю, что есть ошибка в вызове std::advance, когда итератор tmp увеличивается до точно end, что будет допустимо для этого кода. Давайте сосредоточимся на остальных моих вопросах, и я исправлю это. Редактировать 2: теперь нужно исправить?

template<class TInputIterator, class TOutputIterator>
void process_single(TInputIterator& begin, TInputIterator end, TOutputIterator destination)
{
    std::iterator_traits<TInputIterator>::value_type run_length = *begin;
    ++begin;

    // is there a better way to specify run_length elements to accumulate() without having to call advance() here?
    TInputIterator tmp(begin);
    std::advance(tmp, run_length);
    // Edited: this condition should work for the different kinds of iterators?
    if ((end < tmp) || (std::distance(begin, tmp) != run_length))
        throw std::range_error("The input sequence had too few elements.");

    // std::plus is the default accumulate function
    *destination = std::accumulate(begin, tmp, 0/*, std::plus<TInputIterator::value_type>()*/);

    // should I use std::swap(begin, tmp) here instead?
    begin = tmp;
}

Редактировать 3: В ответ на ответы это будет лучше?

template<class TInputIterator, class TOutputIterator>
TInputIterator process_single(TInputIterator begin, TInputIterator end, TOutputIterator destination)
{
    typedef std::iterator_traits<TInputIterator>::value_type value_type;

    value_type run_length = *begin;
    ++begin;

    value_type sum = 0;
    while (run_length > 0 && begin != end)
    {
        sum += *begin;
        ++begin;
        --run_length;
    }

    if (run_length)
    {
        throw std::range_error("The input sequence had too few elements.");
    }

    *destination = sum;

    return begin;
}

template<class TInputIterator, class TOutputIterator>
void process(TInputIterator begin, TInputIterator end, TOutputIterator destination)
{
    while (begin != end)
    {
        begin = process_single(begin, end, destination);
    }
}

Ответы [ 2 ]

2 голосов
/ 01 ноября 2009

Я бы написал этот алгоритм вручную.

Во-первых, функция не принимает входной итератор , потому что они не поддерживают перемещение и расстояние.

Во-вторых, проверка ошибок отключена. Если я не ошибаюсь, возможность end < tmp означает, что было вызвано какое-то неопределенное поведение. Представьте, что контейнер представляет собой std :: list. Что произойдет, если вам удастся продвинуть beyong list.end ()? Но я думаю, что он будет неопределенным даже с вектором или массивом (и MSVC ++, вероятно, сработает с отладкой итератора перед вами).

Итак, чтобы декодировать всю последовательность, я бы сделал что-то вроде этого:

#include <iostream>
#include <algorithm>
#include <vector>
#include <stdexcept>
#include <iterator>

template <class InputIterator, class OutputIterator>
void decode(InputIterator start, InputIterator end, OutputIterator output)
{
    typedef typename std::iterator_traits<InputIterator>::value_type value_type;
    while (start != end)
    {
        value_type count = *start;
        ++start;
        value_type result = value_type();
        for (value_type i = value_type(); i != count; ++i, ++start) {
            if (start == end) {
                throw std::range_error("The input sequence had too few elements.");
            }
            result += *start;
        }
        *output = result;
        ++output;
    }
}

int main()
{
    try {
        std::vector<int> v;
        decode(std::istream_iterator<int>(std::cin), std::istream_iterator<int>(), std::back_inserter(v));
        std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
    }
    catch (const std::exception& e) {
        std::cout << e.what() << '\n';
    }
}
0 голосов
/ 01 ноября 2009
// is there a better way to specify run_length elements to accumulate() without having to call advance() here?

Не совсем.

// Edited: this condition should work for the different kinds of iterators?
if ((end < tmp) || (std::distance(begin, tmp) != run_length))
    throw std::range_error("The input sequence had too few elements.");

Проблема здесь в том, что оператор <работает только с RandomAccessIterators. Почему бы просто: </p>

if (std::distance(tmp, end) < run_length)

// should I use std::swap(begin, tmp) here instead?
begin = tmp;

Нет.

EDIT: I'm aware there's a bug in the call to std::advance where the tmp iterator is incremented to be exactly end, which would be valid for this code. Let's focus on the rest of my questions and I'll fix that. 

Увеличение до конца - стандартное поведение для алгоритмов STL.

void process_single(TInputIterator& begin, TInputIterator end, TOutputIterator destination)

Итераторы STL, как правило, не подходят для передачи byref. Вызывающие слишком часто хотят сохранить их после вызова вашей функции. Например, передача byRef приводит к тому, что это не скомпилируется:

std::vector<something> t;
std::vector<something> t2;
process_single(t.begin(), t.end(), std::back_inserter(t2))

(многие компиляторы возьмут его, но это не стандартно)

Лучше было бы передать значение итератора и затем вернуть новую позицию, в которой вы заканчиваете свой алгоритм, чтобы быть более совместимым с остальной частью STL. Например, смотрите std :: find ().

Надеюсь, это поможет ....

...