Отступ абзаца с кутом - PullRequest
       10

Отступ абзаца с кутом

7 голосов
/ 12 марта 2011

Учитывая строку неизвестной длины, как вы можете вывести ее, используя 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...

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

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

Ответы [ 5 ]

9 голосов
/ 12 марта 2011

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

Первый подход, который является наиболее простым, состоит в том, чтобы прочитать текст в 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.]

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

4 голосов
/ 12 марта 2011

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

#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) - это особый случай. Обычно отступ начинается с нуля. Вызов yourstream.indent(number), где number является положительным или отрицательным, корректирует отступ относительно предыдущего значения. yourstream.indent(0) не будет ничего делать, но я специально выделил его, чтобы сбросить отступ до 0 (как абсолютное значение). Если это получит серьезное применение, я не уверен, что это сработает лучше всего в долгосрочной перспективе, но, по крайней мере, для демоверсии этого будет достаточно.

1 голос
/ 12 марта 2011

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

1 голос
/ 12 марта 2011

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

0 голосов
/ 12 марта 2011

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

{ 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)

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

...