Reputation: 21991
I'm looking for a util which will print a rectangular String[][] into a human-readable table with correct column lengths.
Upvotes: 13
Views: 22367
Reputation: 794
Mine is not as fancy as some of the more elaborate answers, but it's 2024, with many new features added to the JDK. Here's a rather short method to do the trick:
public static <T> void prettyPrint2DArray(T[][] cells) {
int rows = cells.length;
int cols = cells[0].length;
int pad = Arrays.stream(cells).flatMap(Arrays::stream).mapToInt(e -> e.toString().length()).max().orElse(0);
StringBuilder buffer = new StringBuilder();
System.out.printf("%" + pad + "s%n", "-".repeat(((cols * pad) + (2 * cols) + 1)));
// iterate rows
IntStream.range(0, rows).forEach(row -> {
// iterate cols
IntStream.range(0, cols).forEach(col -> buffer.append(
String.format("%s%" + pad + "s %1s", col == 0 ? "|" : "", cells[row][col], "|")));
// back to rows loop
buffer.append(String.format("%n%-" + (pad) + "s%n", "-".repeat(buffer.length())));
System.out.printf("%s", buffer.toString());
buffer.delete(0, buffer.length());
});
}
This method prints to the console a table like this:
------------------------------------
| 30 | 39 | 48 | 1 | 10 | 19 | 28 |
------------------------------------
| 38 | 47 | 7 | 9 | 18 | 27 | 29 |
------------------------------------
| 46 | 6 | 8 | 17 | 26 | 35 | 37 |
------------------------------------
| 5 | 14 | 16 | 25 | 34 | 36 | 45 |
------------------------------------
| 13 | 15 | 24 | 33 | 42 | 44 | 4 |
------------------------------------
| 21 | 23 | 32 | 41 | 43 | 3 | 12 |
------------------------------------
| 22 | 31 | 40 | 49 | 2 | 11 | 20 |
------------------------------------
The padding was initially tailored for a magic square exercise, in which the longest number of digits in a number is predictable. I've edited here recently, to provide a more global approach, and search for the element with the max number of characters, which is required for the padding.
Naturally, you can change the method signature and remove the generics, in case you want to work with primitive types two dimensional arrays. Like so:
public static void prettyPrint2DArray(int[][] cells) {
...
}
Upvotes: 0
Reputation: 78
JakWharton has a nice solution https://github.com/JakeWharton/flip-tables and format String[], List of objects with reflection on property name and result sets. e.g.
List<Person> people = Arrays.asList(new Person("Foo", "Bar"), new Person("Kit", "Kat"));
System.out.println(FlipTableConverters.fromIterable(people, Person.class));
Upvotes: 1
Reputation: 1749
Since I stumbled upon this searching for a ready to use printer (but not only for Strings) I changed Lyubomyr Shaydariv's code written for the accepted answer a bit. Since this probably adds value to the question, I'll share it:
import static java.lang.String.format;
public final class PrettyPrinter {
private static final char BORDER_KNOT = '+';
private static final char HORIZONTAL_BORDER = '-';
private static final char VERTICAL_BORDER = '|';
private static final Printer<Object> DEFAULT = new Printer<Object>() {
@Override
public String print(Object obj) {
return obj.toString();
}
};
private static final String DEFAULT_AS_NULL = "(NULL)";
public static String print(Object[][] table) {
return print(table, DEFAULT);
}
public static <T> String print(T[][] table, Printer<T> printer) {
if ( table == null ) {
throw new IllegalArgumentException("No tabular data provided");
}
if ( table.length == 0 ) {
return "";
}
if( printer == null ) {
throw new IllegalArgumentException("No instance of Printer provided");
}
final int[] widths = new int[getMaxColumns(table)];
adjustColumnWidths(table, widths, printer);
return printPreparedTable(table, widths, getHorizontalBorder(widths), printer);
}
private static <T> String printPreparedTable(T[][] table, int widths[], String horizontalBorder, Printer<T> printer) {
final int lineLength = horizontalBorder.length();
StringBuilder sb = new StringBuilder();
sb.append(horizontalBorder);
sb.append('\n');
for ( final T[] row : table ) {
if ( row != null ) {
sb.append(getRow(row, widths, lineLength, printer));
sb.append('\n');
sb.append(horizontalBorder);
sb.append('\n');
}
}
return sb.toString();
}
private static <T> String getRow(T[] row, int[] widths, int lineLength, Printer<T> printer) {
final StringBuilder builder = new StringBuilder(lineLength).append(VERTICAL_BORDER);
final int maxWidths = widths.length;
for ( int i = 0; i < maxWidths; i++ ) {
builder.append(padRight(getCellValue(safeGet(row, i, printer), printer), widths[i])).append(VERTICAL_BORDER);
}
return builder.toString();
}
private static String getHorizontalBorder(int[] widths) {
final StringBuilder builder = new StringBuilder(256);
builder.append(BORDER_KNOT);
for ( final int w : widths ) {
for ( int i = 0; i < w; i++ ) {
builder.append(HORIZONTAL_BORDER);
}
builder.append(BORDER_KNOT);
}
return builder.toString();
}
private static int getMaxColumns(Object[][] rows) {
int max = 0;
for ( final Object[] row : rows ) {
if ( row != null && row.length > max ) {
max = row.length;
}
}
return max;
}
private static <T> void adjustColumnWidths(T[][] rows, int[] widths, Printer<T> printer) {
for ( final T[] row : rows ) {
if ( row != null ) {
for ( int c = 0; c < widths.length; c++ ) {
final String cv = getCellValue(safeGet(row, c, printer), printer);
final int l = cv.length();
if ( widths[c] < l ) {
widths[c] = l;
}
}
}
}
}
private static <T> String padRight(String s, int n) {
return format("%1$-" + n + "s", s);
}
private static <T> T safeGet(T[] array, int index, Printer<T> printer) {
return index < array.length ? array[index] : null;
}
private static <T> String getCellValue(T value, Printer<T> printer) {
return value == null ? DEFAULT_AS_NULL : printer.print(value);
}
}
And Printer.java:
public interface Printer<T> {
String print(T obj);
}
Usage:
System.out.println(PrettyPrinter.print(some2dArray, new Printer<Integer>() {
@Override
public String print(Integer obj) {
return obj.toString();
}
}));
Why the change to T
? Well String
was not enough, but I don't always want the same output obj.toString()
has to offer, so I used the interface to be able to change that at will.
The second change is not providing the class with an outstream
, well if you want to use the class with a Logger like (Log4j // Logback) you will definitly have a problem using streams.
Why the change to only support static calls? I wanted to use it for loggers, and making just one single call seemed the obvious choice.
Upvotes: 2
Reputation: 21115
If you want something similar to MySQL command-line client output, you can use something like that:
import java.io.PrintStream;
import static java.lang.String.format;
import static java.lang.System.out;
public final class PrettyPrinter {
private static final char BORDER_KNOT = '+';
private static final char HORIZONTAL_BORDER = '-';
private static final char VERTICAL_BORDER = '|';
private static final String DEFAULT_AS_NULL = "(NULL)";
private final PrintStream out;
private final String asNull;
public PrettyPrinter(PrintStream out) {
this(out, DEFAULT_AS_NULL);
}
public PrettyPrinter(PrintStream out, String asNull) {
if ( out == null ) {
throw new IllegalArgumentException("No print stream provided");
}
if ( asNull == null ) {
throw new IllegalArgumentException("No NULL-value placeholder provided");
}
this.out = out;
this.asNull = asNull;
}
public void print(String[][] table) {
if ( table == null ) {
throw new IllegalArgumentException("No tabular data provided");
}
if ( table.length == 0 ) {
return;
}
final int[] widths = new int[getMaxColumns(table)];
adjustColumnWidths(table, widths);
printPreparedTable(table, widths, getHorizontalBorder(widths));
}
private void printPreparedTable(String[][] table, int widths[], String horizontalBorder) {
final int lineLength = horizontalBorder.length();
out.println(horizontalBorder);
for ( final String[] row : table ) {
if ( row != null ) {
out.println(getRow(row, widths, lineLength));
out.println(horizontalBorder);
}
}
}
private String getRow(String[] row, int[] widths, int lineLength) {
final StringBuilder builder = new StringBuilder(lineLength).append(VERTICAL_BORDER);
final int maxWidths = widths.length;
for ( int i = 0; i < maxWidths; i++ ) {
builder.append(padRight(getCellValue(safeGet(row, i, null)), widths[i])).append(VERTICAL_BORDER);
}
return builder.toString();
}
private String getHorizontalBorder(int[] widths) {
final StringBuilder builder = new StringBuilder(256);
builder.append(BORDER_KNOT);
for ( final int w : widths ) {
for ( int i = 0; i < w; i++ ) {
builder.append(HORIZONTAL_BORDER);
}
builder.append(BORDER_KNOT);
}
return builder.toString();
}
private int getMaxColumns(String[][] rows) {
int max = 0;
for ( final String[] row : rows ) {
if ( row != null && row.length > max ) {
max = row.length;
}
}
return max;
}
private void adjustColumnWidths(String[][] rows, int[] widths) {
for ( final String[] row : rows ) {
if ( row != null ) {
for ( int c = 0; c < widths.length; c++ ) {
final String cv = getCellValue(safeGet(row, c, asNull));
final int l = cv.length();
if ( widths[c] < l ) {
widths[c] = l;
}
}
}
}
}
private static String padRight(String s, int n) {
return format("%1$-" + n + "s", s);
}
private static String safeGet(String[] array, int index, String defaultValue) {
return index < array.length ? array[index] : defaultValue;
}
private String getCellValue(Object value) {
return value == null ? asNull : value.toString();
}
}
And use it like that:
final PrettyPrinter printer = new PrettyPrinter(out);
printer.print(new String[][] {
new String[] {"FIRST NAME", "LAST NAME", "DATE OF BIRTH", "NOTES"},
new String[] {"Joe", "Smith", "November 2, 1972"},
null,
new String[] {"John", "Doe", "April 29, 1970", "Big Brother"},
new String[] {"Jack", null, null, "(yes, no last name)"},
});
The code above will produce the following output:
+----------+---------+----------------+-------------------+
|FIRST NAME|LAST NAME|DATE OF BIRTH |NOTES |
+----------+---------+----------------+-------------------+
|Joe |Smith |November 2, 1972|(NULL) |
+----------+---------+----------------+-------------------+
|John |Doe |April 29, 1970 |Big Brother |
+----------+---------+----------------+-------------------+
|Jack |(NULL) |(NULL) |(yes, no last name)|
+----------+---------+----------------+-------------------+
Upvotes: 24
Reputation: 3128
I dont know about a util library that would do this but you can use the String.format function to format the Strings as you want to display them. This is an example i quickly wrote up:
String[][] table = {{"Joe", "Bloggs", "18"},
{"Steve", "Jobs", "20"},
{"George", "Cloggs", "21"}};
for(int i=0; i<3; i++){
for(int j=0; j<3; j++){
System.out.print(String.format("%20s", table[i][j]));
}
System.out.println("");
}
This give the following output:
Joe Bloggs 18
Steve Jobs 20
George Cloggs 21
Upvotes: 8
Reputation: 32953
You can try
System.out.println(Arrays.deepToString(someRectangularStringArray));
And that's as pretty as it'll get without specific code.
Upvotes: 9