2012-07-08 14 views
12

Ich bin auf der Suche nach einem util, die einen rechteckigen String [] [] in eine lesbare Tabelle mit korrekten Spaltenlängen drucken.Pretty print 2D-Array in Java

Antwort

19

Wenn Sie etwas Ähnliches wie MySQL Kommandozeilen-Client ausgeben möchten, können Sie so etwas verwenden:

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

} 

Und es so verwenden:

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

Der obige Code wird die folgende Ausgabe erzeugen:

+----------+---------+----------------+-------------------+ 
|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)| 
+----------+---------+----------------+-------------------+ 
+0

Danke aber funktioniert nicht da ist eine neue Zeile! – Hunsu

+1

@ user230137 true, aber so funktioniert die MySQL-Ausgabe. Wenn Sie im nativen MySQL-Befehlszeilenclient eine SELECT-Abfrage ausführen, die Zeilen mit Zeilenumbruchzeichen ergibt, wird die Ausgabe ebenfalls abgebrochen, da die Ausgabezeichen in keiner Weise verarbeitet werden. Der einzige mir bekannte Unterschied ist, dass meine Implementierung ein wenig kompakter ist, da sie im Gegensatz zur MySQL-Kommandozeilenausgabe kein 1 Leerzeichen horizontales Padding hat. –

8

können Sie

versuchen
System.out.println(Arrays.deepToString(someRectangularStringArray)); 

Und das ist so schön, wie es ohne spezifischen Code zu bekommen.

+0

Das ist nicht wirklich ein Tisch –

+2

@DD. Ich weiß es ist nicht, aber als die ursprüngliche Frage war ein bisschen Licht auf Details wollte ich darauf hinweisen, wie die (afaik) nur No-Code vielleicht gut genug Lösung .... – fvu

+0

Gute Lösung, aber um diese hübscher ich musste einen weiteren 'replace' auf das Ergebnis verwenden, wie hier empfohlen [http://stackoverflow.com/questions/12845208#17423410]. –

9

Ich weiß nicht über eine Util-Bibliothek, die dies tun würde, aber Sie können die String.format -Funktion verwenden, um die Strings zu formatieren, wie Sie sie anzeigen möchten. Dies ist ein Beispiel i schnell schrieb 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(""); 
    } 

Diese geben die folgende Ausgabe:

  Joe    Bloggs     18 
      Steve    Jobs     20 
     George    Cloggs     21 
0

Da ich auf dieser Suche stolperte für ein fertiges Drucker verwenden (aber nicht nur für Streicher) I Lyubomyr Shaydariv ‚s-Code für the accepted answer etwas geschrieben geändert. Da dies wahrscheinlich Wert auf die Frage hinzufügt, werde ich es teilen:

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

} 

Und Printer.java:

public interface Printer<T> { 
    String print(T obj); 
} 

Verbrauch:

System.out.println(PrettyPrinter.print(some2dArray, new Printer<Integer>() { 
     @Override 
     public String print(Integer obj) { 
      return obj.toString(); 
     } 
    })); 

Warum die Änderung T? Nun String war nicht genug, aber ich will nicht immer die gleiche Ausgabe obj.toString() hat zu bieten, also habe ich die Schnittstelle verwendet, um dies nach Belieben ändern zu können.

Die zweite Änderung liefert die Klasse nicht mit outstream, und wenn Sie die Klasse mit einem Logger (Log4j // Logback) verwenden möchten, haben Sie definitiv ein Problem mit Streams.

Warum die Änderung nur statische Aufrufe unterstützen? Ich wollte es für Logger benutzen, und nur einen einzigen Anruf zu tätigen, schien die offensichtliche Wahl zu sein.

+0

Hallo. Danke, dass du auf 'T' gedeutet hast - ich habe nur' '' '' '' ''' '' berücksichtigt. In Bezug auf die statische Methode, die eine 'String' zurückgibt: Ich würde eher eine Writer-Callback-Injektion bereitstellen oder einen' PrintStream' nachahmen, um in einen Logger zu schreiben, da es normalerweise nicht notwendig ist, das Ergebnis eines solchen Drucks zu erhalten, aber es ist notwendig zu schreiben es irgendwo, ohne darauf zu achten, das Ergebnis an ein geeignetes Objekt zu delegieren, das schreiben kann. –

+0

Beachten Sie auch, dass 'String' ein allgemeiner Darstellungstyp ist. 'T' ist nicht und es deckt nur einen einzigen Typ für ein Feld ab, keinen Datensatz. Weil Sie vielleicht eine Liste von Objekten verarbeiten möchten, bei denen jedes Feld in eine "Zeichenfolge" umgewandelt werden könnte. Wenn Sie Zeilen, die aus einer 'List ' oder 'Person []' bestehen, hübsch drucken möchten, wie konvertiert man dann den 'firstName' (' String'), 'lastName' (' String'), ' dateOfBirth' ('Date') Felder getrennt innerhalb' T'? Sie könnten wahrscheinlich eine Druckerschnittstelle vorschlagen, die String [] oder was auch immer zurückgibt, aber im Ergebnis wird immer "String [] []" angezeigt. –

+0

Ich stimme nicht mit Ihrem ersten Kommentar zu PrintStream überein, da dies inkompatibel mit slf4j Loggern ist, aber das könnte ein heiliger Krieg sein, in den ich lieber nicht hineingehe (wie es die vorgeschlagene statische Methode ist). Um Ihre Frage zu beantworten, wird für jedes Objekt, das Sie in den PrettyPrinter einfügen, die Druckerschnittstelle aufgerufen, in der Sie jede Umwandlung von T nach String definieren können, die viel flexibler ist als das Original. Und Sie könnten die Liste iterieren, wenn jedes Feld eine Liste von Personen enthält. Wenn Sie keinen Drucker angeben, wird T.toString() aufgerufen. –

1

JakWharton hat eine schöne Lösung https://github.com/JakeWharton/flip-tables und format String [], Liste der Objekte mit Reflektion auf Eigenschaftenname und Resultsets. z.B.

List<Person> people = Arrays.asList(new Person("Foo", "Bar"), new Person("Kit", "Kat")); 
System.out.println(FlipTableConverters.fromIterable(people, Person.class));