0

I know that "printf" method can use string formatting.

My question is : Is there a way to create a nice looking table with StringBuilder class?

For example:

|Id|Category|Level|Space|Type|Adress|Dimension|Limits|

And under that row, i must add the values of each columns!

Doing something like that : example but with StringBuilder


So the community want to see my answer (which i don't understand why ... but any way i will put it !)

public String toString(){
    StringBuilder s = new StringBuilder();

    s.append("Category: "+this.category+"\t");
    s.append("Level: "+this.level.toString()+"\t");

    return s.toString();
}

Now explain me, why seeing my answer will help me ? I really want to see your answer !

Community
  • 1
  • 1
Damiii
  • 1,363
  • 4
  • 25
  • 46
  • 3
    a) Please show a sample of a "nice looking table". b) Please show what you have already attempted. – merlin2011 Mar 14 '15 at 18:29
  • What is the format of the table you want to create? – Crazyjavahacking Mar 14 '15 at 18:30
  • This guy is trying to do something similar: https://stackoverflow.com/questions/29053779/why-is-my-table-string-format-is-printed-in-one-line – uraimo Mar 14 '15 at 20:37
  • Thank you @uraimo !!!!!!! At least you ain't as the others who post negative vote for nothing and doesn't explain why ! Thank you a lot ! – Damiii Mar 14 '15 at 20:42

2 Answers2

11

A simple approach is, of course, to create hard-wired printf statements. However, this is not very flexible, because you always have to fix the column widths, and the functions will always be specific for one class and its fields.

So I'd like to propose a helper class that mainly does two things:

  • Encapsulates the creation of table cell entries (via a Java 8 Function)
  • Computes the maximum width of each columnm for a given set of elements.

Let there be a given model class, like a Person, for example:

class Person
{
    int getId() { ... }
    String getFirstName() { ... }
    String getLastName() { ... }
    float getHeight()  { ... }
}

Then, I'd like to create a "nice" table as follows:

TableStringBuilder<Person> t = new TableStringBuilder<Person>();
t.addColumn("id", Person::getId);
t.addColumn("first name", Person::getFirstName);
t.addColumn("last name", Person::getLastName);
t.addColumn("height", Person::getHeight);
String s = t.createString(persons);

And I'd expect the contents of this string to be a nicely formatted table:

   id|   first name|    last name|height
-----+-------------+-------------+------
41360|Xvnjhpdqdxvcr|    Stvybcwvm|   1.7
 3503|      Krxvzxk|      Xtspsjd|   1.6
41198|       Uegqfl|  Qlocfljbepo|  1.58
26517|       Somyar|       Aopufo|  1.77
13773| Dxehxjbhwgsm|     Jgnlonjv|  1.77
13067|       Zozitk|       Jbozwd|  1.81
46534|        Bosyq|      Kcprrdc|  1.55
93862|    Rlfxblgqp|   Pgrntaqoos|  1.85
12155|   Kjpjlavsqc|Rxfrrollhwhoh|  1.79
75712|        Fwpnd|     Mwcsshwx|  1.78

Here is a MVCE that shows such a TableStringBuilder and its application:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;

public class TableStringTest
{
    public static void main(String[] args)
    {
        List<Person> persons = new ArrayList<Person>();
        for (int i=0; i<10; i++)
        {
            persons.add(new Person());
        }

        TableStringBuilder<Person> t = new TableStringBuilder<Person>();
        t.addColumn("id", Person::getId);
        t.addColumn("first name", Person::getFirstName);
        t.addColumn("last name", Person::getLastName);
        t.addColumn("height", Person::getHeight);

        String s = t.createString(persons);
        System.out.println(s);
    }
}


class TableStringBuilder<T>
{
    private final List<String> columnNames;
    private final List<Function<? super T, String>> stringFunctions;

    TableStringBuilder()
    {
        columnNames = new ArrayList<String>();
        stringFunctions = new ArrayList<Function<? super T, String>>();
    }

    void addColumn(String columnName, Function<? super T, ?> fieldFunction)
    {
        columnNames.add(columnName);
        stringFunctions.add((p) -> (String.valueOf(fieldFunction.apply(p))));
    }

    private int computeMaxWidth(int column, Iterable<? extends T> elements)
    {
        int n = columnNames.get(column).length();
        Function<? super T, String> f = stringFunctions.get(column);
        for (T element : elements)
        {
            String s = f.apply(element);
            n = Math.max(n, s.length());
        }
        return n;
    }

    private static String padLeft(String s, char c, int length)
    {
        while (s.length() < length)
        {
            s = c + s;
        }
        return s;
    }

    private List<Integer> computeColumnWidths(Iterable<? extends T> elements)
    {
        List<Integer> columnWidths = new ArrayList<Integer>();
        for (int c=0; c<columnNames.size(); c++)
        {
            int maxWidth = computeMaxWidth(c, elements);
            columnWidths.add(maxWidth);
        }
        return columnWidths;
    }

    public String createString(Iterable<? extends T> elements)
    {
        List<Integer> columnWidths = computeColumnWidths(elements);

        StringBuilder sb = new StringBuilder();
        for (int c=0; c<columnNames.size(); c++)
        {
            if (c > 0)
            {
                sb.append("|");
            }
            String format = "%"+columnWidths.get(c)+"s";
            sb.append(String.format(format, columnNames.get(c)));
        }
        sb.append("\n");
        for (int c=0; c<columnNames.size(); c++)
        {
            if (c > 0)
            {
                sb.append("+");
            }
            sb.append(padLeft("", '-', columnWidths.get(c)));
        }
        sb.append("\n");

        for (T element : elements)
        {
            for (int c=0; c<columnNames.size(); c++)
            {
                if (c > 0)
                {
                    sb.append("|");
                }
                String format = "%"+columnWidths.get(c)+"s";
                Function<? super T, String> f = stringFunctions.get(c);
                String s = f.apply(element);
                sb.append(String.format(format, s));
            }
            sb.append("\n");
        }
        return sb.toString();
    }
}


//Dummy Person Class
class Person
{
    private int id;
    private String firstName;
    private String lastName;
    private float height;

    private static Random random = new Random(0);

    Person()
    {
        id = random.nextInt(100000);
        firstName = createRandomString();
        lastName = createRandomString();
        height = (150 + random.nextInt(40)) / 100.0f;
    }

    private static String createRandomString()
    {
        int length = random.nextInt(10) + 5;
        StringBuilder sb = new StringBuilder();
        char offset = 'A';
        for (int i=0; i<length; i++)
        {
            char c = (char)(random.nextInt(26) + offset);
            sb.append(c);
            offset = 'a';
        }
        return sb.toString();
    }

    int getId()
    {
        return id;
    }

    String getFirstName()
    {
        return firstName;
    }

    String getLastName()
    {
        return lastName;
    }

    float getHeight()
    {
        return height;
    }
}
Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • For those visiting this in desperation coming from c# I converted the code above to c# and posted a gist here https://gist.github.com/VisualBean/f1e6d77fc5cb194d994fdbdb66e814a7 – VisualBean Aug 28 '17 at 13:32
3

Ok, so thank you for all the answer who was nice to me.

I figured out that we could do it by that way :

public String toString(){
    StringBuilder s = new StringBuilder();

    s.append(String.format("%-20s%-20s%-20s%-20s%-20s%-20s%-20s\n","Identifier","Category","Level","Space","Type","Dimension","Limits"));
    s.append(String.format("=============================================================================================================================================\n"));
    for(String id : this.idTable.keySet()) {
        s.append(String.format("%-20s",id));
        s.append(this.idTable.get(id).toString());
        //s.append("Identifier: " +id+" "+this.idTable.get(id).toString()+"\n");
    }
    return s.toString();
}

Notice, the String.format which do all the work i wanted and didn't know before !

Regardind to you @Marco13, You did a very good job !!!! Thanks all !

P.S.: I will aprove the message from @Marco13 , because his implementation is very good also !

Damiii
  • 1,363
  • 4
  • 25
  • 46