pearbear
pearbear

Reputation: 1055

How can I easily format my data table in C++?

I'm not sure, but I think I remember there being something in Java that can specify how far from the left of a window that a string or digit begins..

How to easily format a table? I have this (using setw):

Bob Doe     10.96      7.61     14.39      2.11     47.30     14.21     44.58      5.00     60.23
Helen City     10.44      7.78     16.27      1.99     48.92     13.93     53.79      5.00     70.97
Joe Green     10.90      7.33     14.49      2.05     47.91     14.15     44.45      4.70     73.98

and ideally would like:

Bob           Doe        BLR  10.96   7.61  14.39   2.11  47.30  14.21  44.58   5.00  60.23  4:27.47
Helen         City       CUB  10.90   7.33  14.49   2.05  47.91  14.15  44.45   4.70  73.98  4:29.17
Joe           Green      USA  10.44   7.78  16.27   1.99  48.92  13.93  53.79   5.00  70.97  5:06.59

Is the only way calculations? Or is there some magical even more simple way?

Upvotes: 43

Views: 143507

Answers (8)

Louis Go
Louis Go

Reputation: 2578

C++20 includes <format> but it's not supported by libc++ for now.
I suggest to use {fmt} library since it could be obtained easily in Ubuntu20.

According to the doc, you may specify the width as an argument as well.

Format example: {2:<{0}}

`2`   -> Use second arg as value.\
`:`   -> Use non-default format.\
`<`   -> Align to left\
`{0}` -> Use argument 0 as width.

Live Demo

#include <string>
#include <iostream>
#include <fmt/core.h>
#include <tuple>
#include <vector>
int main()
{
    using Row = std::tuple<std::string, std::string, double>;

    std::vector<Row> table = {
        std::make_tuple("Bob", "Doe", 10.96),
        std::make_tuple("Helen", "City", 10.44),
        std::make_tuple("Joe", "Green", 10.90)
    };
    
    size_t nameWidth{12};
    size_t valWidth{7};

    for(const auto& row: table){
        std::cout << fmt::format("{2:<{0}} {3:<{0}} {4:<{1}} \n", 
        nameWidth, valWidth, std::get<0>(row), std::get<1>(row), std::get<2>(row) );
    }
}

Output

Bob          Doe          10.96   
Helen        City         10.44   
Joe          Green        10.9   

Upvotes: 1

Cyril Leroux
Cyril Leroux

Reputation: 2629

In C++, you have three functions to help you do what you want. There are defined in <iomanip>.
- setw() helps you defined the width of the output.
- setfill() Fill the rest with the character you want (in your case ' ').
- left (or right) allow you to define the alignment.

Here is the code to write your first line :

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
    const char separator    = ' ';
    const int nameWidth     = 6;
    const int numWidth      = 8;

    cout << left << setw(nameWidth) << setfill(separator) << "Bob";
    cout << left << setw(nameWidth) << setfill(separator) << "Doe";
    cout << left << setw(numWidth) << setfill(separator) << 10.96;
    cout << left << setw(numWidth) << setfill(separator) << 7.61;
    cout << left << setw(numWidth) << setfill(separator) << 14.39;
    cout << left << setw(numWidth) << setfill(separator) << 2.11;
    cout << left << setw(numWidth) << setfill(separator) << 47.30;
    cout << left << setw(numWidth) << setfill(separator) << 14.21;
    cout << left << setw(numWidth) << setfill(separator) << 44.58;
    cout << left << setw(numWidth) << setfill(separator) << 5.00;
    cout << left << setw(numWidth) << setfill(separator) << 60.23;
    cout << endl;

    cin.get();
}


EDIT : To reduce the code, you can use a template function :

template<typename T> void printElement(T t, const int& width)
{
    cout << left << setw(width) << setfill(separator) << t;
}

That you can use like this :

printElement("Bob", nameWidth);
printElement("Doe", nameWidth);
printElement(10.96, numWidth);
printElement(17.61, numWidth);
printElement(14.39, numWidth);
printElement(2.11, numWidth);
printElement(47.30, numWidth);
printElement(14.21, numWidth);
printElement(44.58, numWidth);
printElement(5.00, numWidth);
printElement(60.23, numWidth);
cout << endl;

Upvotes: 70

synaptik
synaptik

Reputation: 9499

Here are the various functions I use to display data in an organized, tabular form, along with an example demonstrating a possible use scenario.

Because the functions use stringstreams, they aren't as fast as other solutions, but for me that never matters --- the computing bottlekneck is elsewhere.

One advantage of using stringstreams is that the functions alter the precision of their own (internal scope) stringstreams, instead of changing the static cout precision. So you never have to worry about unintentionally modifying precision in a way that persists to affect other parts of your code.


DISPLAYING ARBITRARY PRECISION

This prd function (short for "print double") simply prints a double value with a specified precision.

/* Convert double to string with specified number of places after the decimal. */
std::string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

The following is just a variant that allows you to specify a blank-space padding to the left of the number. This can be helpful in displaying tables.

/* Convert double to string with specified number of places after the decimal
   and left padding. */
std::string prd(const double x, const int decDigits, const int width) {
    stringstream ss;
    ss << fixed << right;
    ss.fill(' ');        // fill space around displayed #
    ss.width(width);     // set  width around displayed #
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

CENTER-ALIGN FUNCTION

This function simply center-aligns text, padding left and right with blank spaces until the returned string is as large as the specified width.

/*! Center-aligns string within a field of width w. Pads with blank spaces
    to enforce alignment. */
std::string center(const string s, const int w) {
    stringstream ss, spaces;
    int padding = w - s.size();                 // count excess room to pad
    for(int i=0; i<padding/2; ++i)
        spaces << " ";
    ss << spaces.str() << s << spaces.str();    // format with padding
    if(padding>0 && padding%2!=0)               // if odd #, add 1 space
        ss << " ";
    return ss.str();
}

EXAMPLE OF TABULAR OUTPUT

So, we could use the prd and center functions above to output a table in the following fashion.

The code:

std::cout << center("x",10)       << " | "
          << center("x^2",10)     << " | "
          << center("(x^2)/8",10) << "\n";

std::cout << std::string(10*3 + 2*3, '-') << "\n";

for(double x=1.5; x<200; x +=x*2) {
    std::cout << prd(x,1,10)       << " | "
              << prd(x*x,2,10)     << " | "
              << prd(x*x/8.0,4,10) << "\n";
}

will print the table:

    x      |    x^2     |  (x^2)/8  
------------------------------------
       1.5 |       2.25 |     0.2812
       4.5 |      20.25 |     2.5312
      13.5 |     182.25 |    22.7812
      40.5 |    1640.25 |   205.0312
     121.5 |   14762.25 |  1845.2812

RIGHT- and LEFT-ALIGN FUNCTIONS

And, of course, you can easily construct variants of the center function that right- or left-align and add padding spaces to fill the desired width. Here are such functions:

/* Right-aligns string within a field of width w. Pads with blank spaces
   to enforce alignment. */
string right(const string s, const int w) {
    stringstream ss, spaces;
    int padding = w - s.size();                 // count excess room to pad
    for(int i=0; i<padding; ++i)
        spaces << " ";
    ss << spaces.str() << s;                    // format with padding
    return ss.str();
}

/*! Left-aligns string within a field of width w. Pads with blank spaces
    to enforce alignment. */
string left(const string s, const int w) {
    stringstream ss, spaces;
    int padding = w - s.size();                 // count excess room to pad
    for(int i=0; i<padding; ++i)
        spaces << " ";
    ss << s << spaces.str();                    // format with padding
    return ss.str();
}

I'm sure there are plenty of more-elegant ways to do this kind of thing --- certainly there are more concise ways. But this is what I do. Works well for me.

Upvotes: 24

blackbada_cpp
blackbada_cpp

Reputation: 432

Just use sprintf with format specifiers to format fields. You can also use MFC CString

#include <iostream>
#include "stdio.h"

using namespace std;

int main()
{
    char buf[256];
    char pattern[]  = "%10s %10s     %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f";
    sprintf(buf, pattern, "Bob",  "Doe",     10.96,      7.61,     14.39,      2.11,     47.30,     14.21,     44.58,      5.00,     60.23);
    cout << buf << endl;
    sprintf(buf, pattern, "Helen", "City",     10.44,      7.78,     16.27,      1.99,     48.92,     13.93,     53.79,      5.00,     70.97);
    cout << buf << endl;
    sprintf(buf, pattern, "Joe", "Green",     10.90,      7.33,     14.49,      2.05,     47.91,     14.15,     44.45,      4.70,     73.98);
    cout << buf << endl;
}

Upvotes: 3

bames53
bames53

Reputation: 88155

I'm not sure what you wrote so I can't see what's wrong, but you can get the results you want with std::setw:

#include <iostream>
#include <iomanip>

int main() {
   std::cout << std::left << std::setw(20) << "BoB" << std::setw(20) << 123.456789 << '\n';
   std::cout << std::left << std::setw(20) << "Richard" << std::setw(20) << 1.0 << '\n';
}

http://ideone.com/Iz5RXr

Upvotes: 0

Aniket Inge
Aniket Inge

Reputation: 25695

Considering an example:

string firstname = "Bob";
string lastname = "Doe";
string country = "BLR";
float f1 = 10.96f, f2=7.61f, f3=14.39f, f4=2.11f, f5=47.30f, f6=14.21f, f7=44.58f, f8=5.00f, f9=60.23f;
string time = "4:27.47";
cout << setw(12) << firstname << set(12) << lastname;
cout << setw(5) << country << setprecision(2) << f1 << setprecision(2) << f2 << setprecision(2) << f3..
  1. use setw() to set the width while printing a string
  2. use setprecision to set the precision for floating values
  3. read MSDN

Upvotes: 0

Rapptz
Rapptz

Reputation: 21317

You could do something like this to simplify the process a bit.

#include <iomanip>
#include <iostream>

struct TableFormat {
    int width;
    char fill;
    TableFormat(): width(14), fill(' ') {}
    template<typename T>
    TableFormat& operator<<(const T& data) {
        std::cout << data << std::setw(width) << std::setfill(fill);
        return *this;
    }
    TableFormat& operator<<(std::ostream&(*out)(std::ostream&)) {
        std::cout << out;
        return *this;
    }
};

int main() {
    TableFormat out;
    out << "Bob" << "Doe";
    out.width = 8;
    out << "BLR" << 10.96 << 7.61 << 14.39 << 2.11 << 47.30;
}

Which would print out (horribly in my case, but it's "customisable" to a degree):

Bob           Doe           BLR   10.96    7.61   14.39    2.11    47.3

The code is pretty self-explanatory, it's just a wrapper around std::cout to allow you to make the tedious calls easier, the second overload for operator<< is to allow you send std::endl..

Upvotes: 2

Alok Save
Alok Save

Reputation: 206518

Assuming you want to format your output to resemble a table, what you need is I/O manipulators.
You can use setw() manipulator to set the output width and setfill() to set the filling character.

Upvotes: 0

Related Questions