DD.
DD.

Reputation: 21991

Pretty print 2D array in Java

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

Answers (6)

Tom Silverman
Tom Silverman

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

Shadi Moadad
Shadi Moadad

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

Sebastian van Wickern
Sebastian van Wickern

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

Lyubomyr Shaydariv
Lyubomyr Shaydariv

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

lilroo
lilroo

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

fvu
fvu

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

Related Questions