2011-03-12 53 views
6

Dada una cadena de longitud desconocida, ¿cómo se puede generar usando cout para que toda la cadena se muestre como un bloque de texto sangrado en la consola? (De modo que incluso si la cadena se ajusta a una nueva línea, la segunda línea tendría el mismo nivel de sangrado)Sangría Párrafo Con cout

Ejemplo:

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; 

Y la salida deseada:

Este es una cadena corta que no está sangrada.

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... 

Editar: asignación La tarea que estoy trabajando es completa. La tarea no tiene nada que ver con hacer que la salida se formatee como en el ejemplo anterior, así que probablemente no debería haber incluido la etiqueta de la tarea. Esto es solo para mi propia iluminación.

Sé que puedo contar a través de los caracteres en la cadena, ver cuando llego al final de una línea, luego escupir una nueva línea y salir -x- número de espacios cada vez. Me interesa saber si existe una forma C++ más simple e idiomática para lograr lo anterior.

+0

¿Qué has intentado hasta ahora? ¿Has escrito el código que lee el texto dado? –

+0

He intentado usar std :: setw(). He escrito el código que lee el texto dado. Estoy tratando de ver si hay una manera simple de hacer que cout acose automáticamente cada línea (incluidas las líneas producidas a partir de un envoltorio de línea) con un número dado de caracteres. –

+0

Posiblemente no debería haber usado la etiqueta de tarea: estoy trabajando en una tarea, pero la tarea no está relacionada con el formato utilizado para la salida. Tenía curiosidad de cómo se podría lograr este formato. –

Respuesta

7

Aquí hay un par de soluciones que funcionarán si está dispuesto a eliminar cualquier espaciado múltiple y/u otro espacio en blanco entre las palabras.

El primer enfoque, que es el más sencillo, sería leer el texto en un istringstream y extraer palabras de la secuencia. Antes de imprimir cada palabra, verifique si la palabra encajará en la línea actual e imprima una nueva línea si no lo hace. Esta implementación particular no manejará las palabras más largas que la longitud máxima de línea correctamente, pero no sería difícil modificarla para dividir palabras largas.

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

Una segunda opción más "avanzada", sería escribir una costumbre ostream_iterator que da formato a las líneas como se espera que sean formateados. Lo llamé ff_ostream_iterator, por "formateo divertido", pero podría nombrarlo algo más apropiado si quisiera usarlo. Esta implementación divide correctamente palabras largas.

Si bien la aplicación iterador es un poco compleja, el uso es bastante sencillo:

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

La implementación real del iterador es el siguiente:

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

[Si copia y pega este fragmento de código y el main desde arriba, debe compilar y ejecutar si su compilador es compatible con C++ 0x std::shared_ptr; puede reemplazar eso con boost::shared_ptr o std::tr1::shared_ptr si su compilador no tiene compatibilidad con C++ 0x todavía.]

Este enfoque es un poco complicado porque los iteradores deben ser copiables y tenemos que estar seguros de que el texto almacenado en el búfer restante solo se imprime exactamente una vez. Hacemos esto confiando en el hecho de que cada vez que se escribe un iterador de salida, ya no se puede usar ninguna copia de este.

+0

Hmm; Ahora me doy cuenta de que perdí la pista del requisito de "las cuerdas cortas no están indentadas". Lo siento por eso; Me desvié un poco. Sin embargo, eso no sería demasiado difícil de implementar, ya que puedes probar la longitud de la cadena de antemano y decidir si imprimirla con sangría o no. –

+0

¿Podría ser tan amable de echar un vistazo a mi respuesta? Lo publiqué bien después de la mayoría de los demás, así que no creo que haya sido visto. Me interesaría especialmente si puedes pensar en algo que eché de menos (o si tengo una buena sugerencia sobre cómo manejar '\ b'; lo he pensado, pero no estoy seguro de todo lo que se necesita, especialmente si retrocede a través de una pestaña, más allá del comienzo de la línea, etc.) –

+0

Ah, otra cosa: para la sangría, ¿crees que es mejor usar caracteres espaciales, o lo que sea que se haya establecido el carácter 'relleno' para la transmisión? ¿a? –

1

no estoy seguro de que este es el camino para hacerlo, pero si usted sabe o puede asumir el ancho de la pantalla, lo primero que pensé es eliminar los primeros screenWidth - indent caracteres de la cadena e imprimirlos con los espacios precedentes , y sigue haciéndolo hasta que hayas hecho toda la cadena.

1

Siempre puede usar '\t' para sangrar la línea en lugar de un número determinado de caracteres, pero no conozco una forma más sencilla de implementar la lógica sin introducir bibliotecas externas.

0

Este problema se puede reducir a una tarea de minimizar la cantidad de espacio desperdiciado en cada línea. Supongamos que tenemos palabras de longitudes siguientes

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

Si calculamos la cantidad de espacio que se desperdicia cuando disponemos últimos n palabras sin romperlos y lo ponemos en una tabla, entonces podemos encontrar el número óptimo de palabras para imprimir en la línea actual en el futuro.

Aquí hay un ejemplo de pantalla panorámica de 20 caracteres. En esta tabla primera columna es el número de las últimas palabras, la segunda columna es la longitud de la palabra enésimo desde el extremo y la tercera es el mínimo espacio desperdiciado:

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

Por ejemplo, cuando sólo tenemos una última palabra de 10 letras Se desperdician 10 letras, si tenemos 2 palabras con un segundo desde el final, 4 caracteres de largo, tendremos 5 letras desperdiciadas (un espacio entre palabras), la palabra adicional de 3 letras dejará solo un espacio desperdiciado. Agregar otra palabra de 10 letras nos deja con 11 letras desperdiciadas en total en 2 líneas y así sucesivamente.

Ejemplo

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

Si elegimos para imprimir 2 palabras en primera línea de espacio desperdiciado es de hecho 14. Los números en() espectáculo espacio desperdiciado.

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

Creo que este es un problema bien conocido y el algoritmo que he descrito es un ejemplo de programación dinámica.

4

Esto todavía podría utilizar un poco de trabajo (por ejemplo, la indent realmente debería implementarse como un manipulador, pero manipuladores con argumentos son duro para escribir portable - el estándar no soporta realmente/definirlos) . Creo que también necesita un poco de trabajo sobre cómo maneja las nuevas líneas, y [Editar: Creo que he solucionado los problemas que vi allí.] Probablemente hay al menos un par de casos de esquina que no son perfecto [Editar: por ejemplo, en este momento, trata la parte de atrás como si fuera un personaje normal].

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

Tenga en cuenta que indent(0) es un caso especial. Normalmente, la sangría comienza en 0. llamando al yourstream.indent(number) donde number es positivo o negativo ajusta la sangría en relación con el valor anterior.yourstream.indent(0)no haría nada, pero lo he guardado para restablecer la sangría a 0 (como absoluto). Si esto se utiliza seriamente, no estoy seguro de que funcione mejor a largo plazo, pero al menos para la demostración parece bastante conveniente.

+0

Muy agradable. Un enfoque basado en 'streambuf' definitivamente tiene más sentido que' stream_iterator'. Puede ser más limpio tener un 'reset_indent (int)' en lugar del 0 hack. No estoy seguro sobre el manejo '\ b': supongo que el enfoque más natural sería mantener el almacenamiento en búfer hasta que 'overflow' obtenga' \ r' o '\ n' ya que usted (¿usualmente?) No puede retroceder a través de un nueva línea, pero luego puede terminar haciendo una gran cantidad de memoria intermedia innecesaria. Esa es una buena pregunta sobre los espacios frente al personaje de relleno; Supongo que usar el personaje de relleno sería más "flexible", ya que aún podrías establecer eso en un espacio. –