7

長さが不明な文字列が与えられた場合、cout を使用してそれを出力し、文字列全体がインデントされたテキスト ブロックとしてコンソールに表示されるようにするにはどうすればよいでしょうか? (そのため、文字列が改行されても、2 行目のインデントは同じレベルになります)

例:

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++ の方法があるかどうか知りたいです。

4

5 に答える 5

11

単語間の複数のスペースやその他の空白を破棄しても構わないと思っている場合に機能する解決策をいくつか示します。

最も単純な最初のアプローチは、テキストを に読み込み、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;
}

2 番目のより「高度な」オプションは、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_ptrboost::shared_ptrまたはstd::tr1::shared_ptr、コンパイラがまだ C++0x をサポートしていない場合は、これを置き換えることができます。]

イテレータはコピー可能である必要があり、残りのバッファリングされたテキストが正確に 1 回だけ出力されるようにする必要があるため、このアプローチは少し注意が必要です。これは、出力反復子が書き込まれるたびに、そのコピーが使用できなくなるという事実に依存することによって行われます。

于 2011-03-12T06:15:47.287 に答える
5

これにはまだ少し作業が必要になる可能性があります (たとえば、indentおそらくマニピュレータとして実装する必要がありますが、引数を持つマニピュレータは移植可能に記述するのが難しく、標準ではそれらを実際にサポート/定義していません)。おそらく、完璧ではないコーナー ケースが少なくとも 2 つあります (たとえば、現在、バック スペースは通常の文字のように扱われます)。

#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 から始まります。where がyourstream.indent(number)numberまたは負のいずれかであると、前の値を基準にしてインデントが調整されます。yourstream.indent(0) 何もしませんが、インデントを 0 にリセットするように特殊なケースにしました (絶対値として)。これが本格的に使用された場合、長期的にはうまくいくかどうかはわかりませんが、少なくともデモでは十分に見えます.

于 2011-03-12T10:44:57.910 に答える
1

これが正しい方法かどうかはわかりませんが、画面の幅を知っている、または想定できる場合、最初に考えたのはscreenWidth - indent、文字列から最初の文字を削除し、前のスペースでそれらを出力し、それを実行し続けることです。文字列全体を実行しました。

于 2011-03-12T06:13:29.613 に答える
1

'\t'設定された文字数の代わりに行をインデントするためにいつでも使用できますが、外部ライブラリを導入せずにロジックを実装する簡単な方法を私は知りません.

于 2011-03-12T06:14:10.100 に答える
0

この問題は、各行の無駄なスペースの量を最小限に抑えるタスクに減らすことができます。次の長さの単語があるとします

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

最後のn個の単語を壊さずに並べてテーブルに入れるときに無駄になるスペースの量を計算すると、現在の行に印刷するのに最適な単語数を見つけることができます。

これは、20文字のワイドスクリーンの例です。この表では、最初の列は最後の単語の数、2番目の列は最後からn番目の単語の長さ、3番目は無駄になる最小のスペースです。

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文字の最後の単語が1つしかない場合、10文字が無駄になります。最後から2番目の単語が4文字の長さの場合、5文字が無駄になります(単語間のスペースが1つ)。余分な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)

これはよく知られた問題であり、私が説明したアルゴリズムは動的計画法の例だと思います。

于 2011-03-12T08:32:55.090 に答える