使用 cout 缩进段落

发布于 2024-10-21 17:41:40 字数 718 浏览 1 评论 0原文

给定一个未知长度的字符串,如何使用 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;

以及所需的输出:

这是一个不缩进的短字符串。

 这是一个很长的字符串,将
    换行到下一行,因为它是
    非常长的字符串,将缠绕到
    下一行...

编辑:我正在做的家庭作业已经完成。该作业与将输出格式化为上面的示例中的格式无关,因此我可能不应该包含作业标签。这只是为了我个人的启示。

我知道我可以计算字符串中的字符,看看何时到达行尾,然后吐出换行符并每次输出 -x- 个空格。我很想知道是否有一种更简单、惯用的 C++ 方法来完成上述任务。

Given a string of unknown length, how can you output it using cout so that the entire string displays as an indented block of text on the console? (so that even if the string wraps to a new line, the second line would have the same level of indentation)

Example:

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;

And the desired output:

This is a short string that isn't indented.

    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: The homework assignment I'm working on is complete. The assignment has nothing to do with getting the output to format as in the above example, so I probably shouldn't have included the homework tag. This is just for my own enlightment.

I know I could count through the characters in the string, see when I get to the end of a line, then spit out a newline and output -x- number of spaces each time. I'm interested to know if there is a simpler, idiomatic C++ way to accomplish the above.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

神回复 2024-10-28 17:41:40

如果您愿意删除单词之间的任何多个空格和/或其他空格,这里有一些可行的解决方案。

第一种方法是最直接的,是将文本读入 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,则编译并运行;如果您的编译器尚不支持 C++0x,您可以将其替换为 boost::shared_ptrstd::tr1::shared_ptr

]有点棘手,因为迭代器必须是可复制的,并且我们必须确保任何剩余的缓冲文本仅打印一次。我们通过以下事实来做到这一点:每当写入输出迭代器时,它的任何副本都不再可用。

Here are a couple of solutions that will work if you are willing to throw out any multiple spacing and/or other whitespace between words.

The first approach, which is the most straightforward, would be to read the text into an istringstream and extract words from the stream. Before printing each word, check to see whether the word will fit on the current line and print a newline if it won't. This particular implementation won't handle words longer than the maximum line length correctly, but it wouldn't be difficult to modify it to split long words.

#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;
}

A second, more "advanced" option, would be to write a custom ostream_iterator that formats lines as you expect them to be formatted. I've named this ff_ostream_iterator, for "funny formatting," but you could name it something more appropriate if you wanted to use it. This implementation does correctly split long words.

While the iterator implementation is a bit complex, the usage is quite straightforward:

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));
}

The actual implementation of the iterator is as follows:

#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_;
};

[If you copy and paste this code snippet and the main from above it, it should compile and run if your compiler supports the C++0x std::shared_ptr; you can replace that with boost::shared_ptr or std::tr1::shared_ptr if your compiler doesn't have C++0x support yet.]

This approach is a bit tricky because iterators have to be copyable and we have to be sure that any remaining buffered text is only printed exactly once. We do this by relying on the fact that any time an output iterator is written to, any copies of it are no longer usable.

━╋う一瞬間旳綻放 2024-10-28 17:41:40

这仍然需要一些工作(例如,缩进可能应该作为操纵器来实现,但是带有参数的操纵器很难可移植地编写 - 标准没有并不是真正支持/定义它们)。可能至少有一些不完美的极端情况(例如,现在,它将退格视为正常字符)。

#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(绝对值)。如果这得到认真使用,我不确定从长远来看会产生最好的结果,但至少对于演示来说它似乎足够了。

This could still use a little bit of work (e.g., the indent should probably be implemented as a manipulator, but manipulators with arguments are hard to write portably -- the standard doesn't really support/define them). There are probably at least a couple of corner cases that aren't perfect (e.g., right now, it treats back-space as if it were a normal character).

#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.";
}

Note that indent(0) is a special case. Normally indentation starts out at 0. calling yourstream.indent(number) where number is either positive or negative adjusts the indentation relative to the previous value. yourstream.indent(0) wouldn't do anything, but I've special-cased it to reset the indentation to 0 (as an absolute). If this gets put to serious use, I'm not sure that'll work out the best in the long term, but at least for the demo it appears sufficient.

泪眸﹌ 2024-10-28 17:41:40

我不确定这是否是这样做的方法,但如果您知道或可以假设屏幕宽度,我的第一个想法是删除第一个 screenWidth - indent 字符并用前面的空格打印它们,并继续这样做,直到完成整个字符串。

I'm not sure this is the way to do it, but if you know or can assume the screen width, my first thought is to remove the first screenWidth - indent chars from the string and print them with the preceding spaces, and keep doing that until you've done the whole string.

北斗星光 2024-10-28 17:41:40

您始终可以使用 '\t' 来缩进该行而不是一组字符,但我知道没有更简单的方法可以在不引入外部库的情况下实现逻辑。

You could always use '\t' to indent the line instead of a set number of characters, but I know of no simpler way to implement the logic without introducing external libraries.

深海夜未眠 2024-10-28 17:41:40

这个问题可以简化为最小化每行浪费空间量的任务。
假设我们有以下长度的单词,

{ 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 个字母单词将只浪费 1 个空格。添加另外 10 个字母的单词后,我们在 2 行中总共浪费了 11 个字母,依此类推。

示例

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)

我认为这是一个众所周知的问题,我描述的算法是动态规划的一个例子。

This problem can be reduced to a task of minimizing amount of wasted space on each line.
Suppose we have words of following lengths

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

If we calculate amount of space that will be wasted when we arrange last n words without breaking them and put it into a table then we can find optimum number of words to print on current line going forward.

Here is an example for 20 character wide screen. In this table first column is number of last words, second column is length of n'th word from the end and third is the minimum space wasted:

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

For example when we have only one last word of 10 letters 10 letters are wasted, if we have 2 words with second from the end 4 characters long we will have 5 letters wasted (one space between words) extra 3 letter word will leave only one spaces wasted. Adding another 10 letter word leaves us with 11 letters wasted total on 2 lines and so on.

Example

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

If we choose to print 2 words on first line wasted space is indeed 14. Numbers in () show wasted space.

6, 7 (6)
5, 8 (6)
10, 3, 4 (2)
4, 10 (6)

I think this is a well known problem and algorithm I have described is an example of dynamic programming.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文