PrevUpHomeNext

segmented_vector

This segmented_vector API is very similar to that of unencoded_rope, except that is supports more types than char for the element-type.

Though segmented_vector is not meant to be a string-like type, it has a replace() member, since it is considerably more efficient to do a replace operation than to do an erasure followed by an insertion.

Otherwise, a few interface changes were made from the unencoded_rope interface to make the type more like std::vector.

One nice use case for this template is writing undo systems:

#include <boost/text/segmented_vector.hpp>

#include <iostream>
#include <vector>


// This is a general-purpose undo/redo system.  It consists of a vector of
// states, which is mostly used as a stack, but which we can also iterate
// over.  We also have an iterator, which always points to a valid element of
// our state vector, indicating the current state.

// Undo can then be implemented by simply decrementing the current-state
// iterator; redo is performed by incrementing it.

// Copying our entire working state is potentially very expensive, of course.
// The data sharing inherent to segmented_vector's operation means that
// copying or mutating an entire segmented_vector mostly shares data, making
// those program state copies cheap.

// This gets a new undo state, by erasing any pending redos (more recent
// states), then copying the most recent state and returning an iterator to
// the copy.
template<typename T>
typename std::vector<boost::text::segmented_vector<T>>::iterator new_undo_state(
    std::vector<boost::text::segmented_vector<T>> & history,
    typename std::vector<boost::text::segmented_vector<T>>::iterator it)
{
    history.erase(std::next(it), history.end());
    return history.insert(history.end(), history.back());
}

// Undo just decrements the current-state iterator, except that undos that
// would underflow are ignored.
template<typename T>
typename std::vector<boost::text::segmented_vector<T>>::iterator undo(
    std::vector<boost::text::segmented_vector<T>> const & history,
    typename std::vector<boost::text::segmented_vector<T>>::iterator it)
{
    if (it != history.begin())
        --it;
    return it;
}

// Like undo, in reverse.
template<typename T>
typename std::vector<boost::text::segmented_vector<T>>::iterator redo(
    std::vector<boost::text::segmented_vector<T>> const & history,
    typename std::vector<boost::text::segmented_vector<T>>::iterator it)
{
    if (std::next(it) != history.end())
        ++it;
    return it;
}

// Gets a new state to work on by calling new_undo_state(), and then applies
// an arbitrary mutation to the new state.
template<typename T, typename Func>
typename std::vector<boost::text::segmented_vector<T>>::iterator mutate_state(
    std::vector<boost::text::segmented_vector<T>> & history,
    typename std::vector<boost::text::segmented_vector<T>>::iterator it,
    Func && f)
{
    it = new_undo_state(history, it);
    *it = std::forward<Func>(f)(*it);
    return it;
}

// Print the history, latest to oldest.
template<typename T>
void dump_history(
    std::vector<boost::text::segmented_vector<T>> const & history,
    typename std::vector<boost::text::segmented_vector<T>>::iterator it)
{
    std::cout << "========================================\n";
    int i = 0;
    for (auto first = history.begin(), last = history.end(); last-- != first;) {
        if (last == it)
            std::cout << " -> ";
        else
            std::cout << "    ";
        std::cout << "history " << i++ << ": [ ";
        for (auto & x : *last) {
            std::cout << x << ' ';
        }
        std::cout << "]\n";
    }
    std::cout << "\n";
}


int main ()
{
    using state_type = boost::text::segmented_vector<int>;

    // Here, we push a single value onto the end of the segmented_vector that
    // represents our current state.  This is a cheap operation even if the
    // current state contains billions of elements.
    int i = 0;
    auto push_next = [&i](state_type state) {
        state.insert(state.end(), i++);
        return state;
    };

    // Start with one empty history entry.
    std::vector<state_type> undo_history(1);
    std::vector<state_type>::iterator current_state_it = undo_history.begin();

    // Shows current state as "[ ]".
    dump_history(undo_history, current_state_it);

    // Copies the initial empty state; pushes a 0 onto the end of the copy;
    // returns an iterator to the result.
    current_state_it = mutate_state(undo_history, current_state_it, push_next);

    // Pushes a 1 onto the end of another copy.
    current_state_it = mutate_state(undo_history, current_state_it, push_next);

    // Shows current state as "[ 0 1 ]".
    dump_history(undo_history, current_state_it);

    // Conditionally decrements a pointer (pointing back to the initial empty
    // state).
    current_state_it = undo(undo_history, current_state_it);

    // Shows current state as "[ 0 ]", with one more recent state "[ 0 1 ]".
    dump_history(undo_history, current_state_it);

    // Conditionally increments a pointer (pointing back to the result of the first operation).
    current_state_it = redo(undo_history, current_state_it);

    // Shows current state as "[ 0 1 ]" again.
    dump_history(undo_history, current_state_it);

    // Unodes one state, pushes a 2 onto the end of another copy.  The push
    // clears any history that is after the current one.
    current_state_it = undo(undo_history, current_state_it);
    current_state_it = mutate_state(undo_history, current_state_it, push_next);

    // Shows current state as "[ 0 2 ]", with no more recent states.
    dump_history(undo_history, current_state_it);
}

I don't know if you've ever written an undo system that can do, undo, or redo any command you can think of; it's usually a lot of code. This one is less than 40 lines.


PrevUpHomeNext