Отступ абзаца с cout


Учитывая строку неизвестной длины, как вы можете вывести ее с помощью cout так, чтобы вся строка отображалась в виде отступа блока текста на консоли? (так что даже если строка обернется в новую строку, вторая строка будет иметь тот же уровень отступа)

Пример:

cout << "This is a short string that isn't indented." << endl;
cout << /* Indenting Magic */ << "This is a very long string that will wrap to the next line because it is a very long string that will wrap to the next line..." << endl;

И желаемый результат:

Это короткая строка, которая не имеет отступа.

    This is a very long string that will
    wrap to the next line because it is a
    very long string that will wrap to the
    next line...

Edit: домашнее задание, над которым я работаю, завершено. Назначение не имеет ничего общего с получением выходных данных в формате, как в приведенном выше примере, поэтому я, вероятно, не должен был включать тег домашнего задания. Это только для моего собственного просветления.

Я знаю, что могу пересчитать символы в строке, посмотреть, когда я доберусь до конца строки, а затем выплюнуть новую строку и вывести-x - количество пробелов каждый раз. Мне интересно знать, существует ли более простой, идиоматический способ C++ для выполнения вышеизложенного.
5 6

5 ответов:

Вот несколько решений, которые будут работать, если вы готовы выбросить любой множественный интервал и/или другие пробелы между словами.

Первый подход, который является наиболее простым, заключается в том, чтобы читать текст в istringstream и извлекать слова из потока. Перед печатью каждого слова проверьте, поместится ли слово в текущей строке, и выведите новую строку, если это не так. эта конкретная реализация не будет обрабатывать слова длиннее максимальной длины строки правильно, но было бы не трудно изменить его, чтобы разделить длинные слова.
#include <iostream>
#include <sstream>
#include <string>

int main() {
    const unsigned max_line_length(40);
    const std::string line_prefix("    ");

    const std::string text(
        "Friends, Romans, countrymen, lend me your ears; I come to bury Caesar,"
        " not to praise him.  The evil that men do lives after them; The good "
        "is oft interred with their bones; So let it be with Caesar.");

    std::istringstream text_iss(text);

    std::string word;
    unsigned characters_written = 0;

    std::cout << line_prefix;
    while (text_iss >> word) {

        if (word.size() + characters_written > max_line_length) {
            std::cout << "\n" << line_prefix;
            characters_written = 0;
        }

        std::cout << word << " ";
        characters_written += word.size() + 1;
    }
    std::cout << std::endl;
}
Второй, более "продвинутый" вариант-написать пользовательский ostream_iterator, который форматирует строки так, как вы ожидаете их форматирования. Я назвал это ff_ostream_iterator, для "забавного форматирования", но вы могли бы назвать его более подходящим, если бы хотели его использовать. Эта реализация правильно разбивает длинные слова.

Хотя реализация итератора немного сложна, использование довольно прямолинейно:

int main() {
    const std::string text(
        "Friends, Romans, countrymen, lend me your ears; I come to bury Caesar,"
        " not to praise him.  The evil that men do lives after them; The good "
        "is oft interred with their bones; So let it be with Caesar. ReallyLong"
        "WordThatWontFitOnOneLineBecauseItIsSoFreakinLongSeriouslyHowLongIsThis"
        "Word");

    std::cout << "    ========================================" << std::endl;

    std::copy(text.begin(), text.end(), 
              ff_ostream_iterator(std::cerr, "    ", 40));
}

Фактическая реализация итератора выглядит следующим образом:

#include <cctype>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <string>

class ff_ostream_iterator 
    : public std::iterator<std::output_iterator_tag, char, void, void, void>
{
public:

    ff_ostream_iterator() { }

    ff_ostream_iterator(std::ostream& os,
                        std::string line_prefix, 
                        unsigned max_line_length)
        : os_(&os),
          line_prefix_(line_prefix), 
          max_line_length_(max_line_length),
          current_line_length_(),
          active_instance_(new ff_ostream_iterator*(this))
    { 
        *os_ << line_prefix;
    }

    ~ff_ostream_iterator() {
        if (*active_instance_ == this)
            insert_word();
    }

    ff_ostream_iterator& operator=(char c) {
        *active_instance_ = this;
        if (std::isspace(c)) {
            if (word_buffer_.size() > 0) {
                insert_word();
            }
        }
        else {
            word_buffer_.push_back(c);
        }
        return *this;
    }

    ff_ostream_iterator& operator*()     { return *this; }
    ff_ostream_iterator& operator++()    { return *this; }
    ff_ostream_iterator  operator++(int) { return *this; }


private:

    void insert_word() {
        if (word_buffer_.size() == 0)
            return; 

        if (word_buffer_.size() + current_line_length_ <= max_line_length_) {
            write_word(word_buffer_);
        }
        else { 
            *os_ << '\n' << line_prefix_;

            if (word_buffer_.size() <= max_line_length_) {
                current_line_length_ = 0;
                write_word(word_buffer_);
            }
            else {
                for (unsigned i(0);i<word_buffer_.size();i+=max_line_length_) 
                {
                    current_line_length_ = 0;
                    write_word(word_buffer_.substr(i, max_line_length_));
                    if (current_line_length_ == max_line_length_) {
                        *os_ << '\n' << line_prefix_;
                    }
                }
            }
        }

        word_buffer_ = "";
    }

    void write_word(const std::string& word) {
        *os_ << word;
        current_line_length_ += word.size();
        if (current_line_length_ != max_line_length_) {
            *os_ << ' ';
            ++current_line_length_;
        }
    }

    std::ostream* os_;
    std::string word_buffer_;

    std::string line_prefix_;
    unsigned max_line_length_;
    unsigned current_line_length_;

    std::shared_ptr<ff_ostream_iterator*> active_instance_;
};

[если вы копируете и вставляете этот фрагмент кода и main сверху, он должен компилироваться и запускаться, если ваш компилятор поддерживает C++0x std::shared_ptr; Вы можете заменить его на boost::shared_ptr или std::tr1::shared_ptr, Если ваш компилятор еще не поддерживает C++0x.]

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

Для этого все еще может потребоваться немного работы (например, indent действительно должен быть реализован как манипулятор, но манипуляторы с аргументами трудно написать переносимо-стандарт на самом деле не поддерживает/определяет их). я считаю, что ему также нужно немного поработать над тем, как он обрабатывает новые строки, и[Edit:я думаю, что исправил проблемы, которые я там увидел.] Вероятно, есть, по крайней мере, пара угловых случаев, которые не идеальны [Edit: например, прямо сейчас он рассматривает заднее пространство, как если бы это был нормальный персонаж].

#include <iostream>
#include <streambuf>
#include <iomanip>

class widthbuf: public std::streambuf {
public:
    widthbuf(int w, std::streambuf* s): indent_width(0), def_width(w), width(w), sbuf(s), count(0) {}
    ~widthbuf() { overflow('\n'); }
    void set_indent(int w) { 
        if (w == 0) {
            prefix.clear();
            indent_width = 0;
            width = def_width;
        }
        else {
            indent_width += w; 
            prefix = std::string(indent_width, ' ');
            width -= w; 
        }
    }
private:
    typedef std::basic_string<char_type> string;

    // This is basically a line-buffering stream buffer.
    // The algorithm is: 
    // - Explicit end of line ("\r" or "\n"): we flush our buffer 
    //   to the underlying stream's buffer, and set our record of
    //   the line length to 0.
    // - An "alert" character: sent to the underlying stream
    //   without recording its length, since it doesn't normally
    //   affect the a appearance of the output.
    // - tab: treated as moving to the next tab stop, which is
    //   assumed as happening every tab_width characters. 
    // - Everything else: really basic buffering with word wrapping. 
    //   We try to add the character to the buffer, and if it exceeds
    //   our line width, we search for the last space/tab in the 
    //   buffer and break the line there. If there is no space/tab, 
    //   we break the line at the limit.
    int_type overflow(int_type c) {
        if (traits_type::eq_int_type(traits_type::eof(), c))
            return traits_type::not_eof(c);
        switch (c) {
        case '\n':
        case '\r': {
                        buffer += c;
                        count = 0;
                        sbuf->sputn(prefix.c_str(), indent_width);
                        int_type rc = sbuf->sputn(buffer.c_str(), buffer.size());
                        buffer.clear();
                        return rc;
                   }
        case '\a':
            return sbuf->sputc(c);
        case '\t':
            buffer += c;
            count += tab_width - count % tab_width;
            return c;
        default:
            if (count >= width) {
                size_t wpos = buffer.find_last_of(" \t");
                if (wpos != string::npos) {
                    sbuf->sputn(prefix.c_str(), indent_width);
                    sbuf->sputn(buffer.c_str(), wpos);
                    count = buffer.size()-wpos-1;
                    buffer = string(buffer, wpos+1);
                }
                else {
                    sbuf->sputn(prefix.c_str(), indent_width);
                    sbuf->sputn(buffer.c_str(), buffer.size());
                    buffer.clear();
                    count = 0;
                }
                sbuf->sputc('\n');
            }
            buffer += c;
            ++count;
            return c;
        }
    }

    size_t indent_width;
    size_t width, def_width;
    size_t count;
    size_t tab_count;
    static const int tab_width = 8;
    std::string prefix;

    std::streambuf* sbuf;

    string buffer;
};

class widthstream : public std::ostream {
    widthbuf buf;
public:
    widthstream(size_t width, std::ostream &os) : buf(width, os.rdbuf()), std::ostream(&buf) {}
    widthstream &indent(int w) { buf.set_indent(w); return *this; }
};

int main() {
    widthstream out(30, std::cout);
    out.indent(10) << "This is a very long string that will wrap to the next line because it is a very long string that will wrap to the next line.\n";
    out.indent(0) << "This is\tsome\tmore text that should not be indented but should still be word wrapped to 30 columns.";
}
Обратите внимание, что indent(0) является частным случаем. Обычно отступ начинается с 0. вызов yourstream.indent(number), где number является положительным или отрицательным, регулирует отступ относительно предыдущего значения. yourstream.indent(0) я бы ничего не сделал, но я специально сделал это, чтобы сбросить отступ до 0 (как абсолют). Если это будет серьезно использовано, я не уверен, что это сработает наилучшим образом в долгосрочной перспективе, но, по крайней мере, для демо-версии это выглядит довольно удобный.

Я не уверен, что это способ сделать это, но если вы знаете или можете предположить ширину экрана, моя первая мысль-удалить первые символы screenWidth - indent из строки и распечатать их с предыдущими пробелами, и продолжать делать это, пока вы не сделаете всю строку.

Вы всегда можете использовать '\t' для отступа строки вместо заданного числа символов, но я не знаю более простого способа реализации логики без введения внешних библиотек.

Эту задачу можно свести к задаче минимизации количества потраченного впустую пространства на каждой строке. Предположим, что у нас есть слова следующей длины

{ 6,7,6,8,10,3,4,10 }

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

Вот пример для 20-символьного широкого экрана. В этой таблице первый столбец-это количество последних слов, второй столбец-это длина n-го слова от конца и третьего-это минимальное потраченное пространство:

8 6 1
7 7 7
6 5 14
5 8 2
4 10 11
3 3 1 
2 4 5
1 10 10

Например, когда у нас есть только одно последнее слово из 10 букв 10 букв пропадают впустую, если у нас есть 2 слова со вторым от конца 4 символов длиной мы будем иметь 5 букв впустую (один пробел между словами) дополнительные 3 буквы слово оставит только один пробел впустую. Добавление еще 10 букв слова оставляет нас с 11 буквами впустую всего на 2 строчки и так далее.

Пример

6, 7, 5 (0)
8, 10 (1)
3, 4, 10 (1) 

Если мы решим напечатать 2 слова на первой строке пустого места действительно 14. Числа в () показывают потерянное пространство.

6, 7 (6)
5, 8 (6)
10, 3, 4 (2)
4, 10 (6)
Я думаю, что это хорошо известная проблема, и алгоритм, который я описал, является примером динамического программирования.