If you'd like to pivot this:
┌────────────────────┐
│One Two Three Four│
╞════════════════════╡
│One 2 3 4│
│Five 6 7 8│
│Nine [10] 11 12 │
└────────────────────┘
to this:
┌───────────────────┐
│One One Five Nine│
╞═══════════════════╡
│Two 2 6 [10]│
│Three 3 7 11│
│Four 4 8 12 │
└───────────────────┘
using this:
Table table = new Table()
.addLine("One", "Two", "Three", "Four")
.addLine("One", "2", "3", "4")
.addLine("Five", "6", "7", "8")
.addLine("Nine", "[10]", "11", "12")
.boxWith(
"┌─┐" +
"╞═╡" +
"│ │" +
"└─┘"
)
.alignRight()
.alignLeft(0)
.alignRight(1)
.alignCenter(2)
.alignLeft(3,3)
.boxHeader()
;
System.out.println(table);
Table pivot = table
.toPivot()
.alignRight()
.alignLeft(0)
.alignRight(1)
.alignCenter(2)
.alignLeft(3,3)
;
System.out.println(pivot);
All you need is:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import java.io.PrintStream;
public class Table {
private final List<Column> columns = new ArrayList<>();
// Defaults
private String columnSeparator = " ";
private char pad = ' ';
private PrintStream out = System.out;
private Table.Alignment alignment;
private static final Table.Alignment LEFT = (left, word, right) -> word + left + right;
private static final Table.Alignment CENTER = (left, word, right) -> left + word + right;
private static final Table.Alignment RIGHT = (left, word, right) -> left + right + word;
private String boxCharacters;
private BoxType boarder;
{
alignLeft(); // Set default alignment
boxWith( // Set default boxing characters
".-." +
"|=|" +
"| |" +
"'-'"
);
boxOff(); // Set default boxing boarder
}
public Table addLine(String... line) {
if ( columns.size() > 0 && columns.size() != line.length) {
throw new IllegalArgumentException("Inconsistant arg. count.");
}
if ( columns.size() == 0 ) {
for (int i = 0; i < line.length; i++) {
columns.add( new Table.Column(alignment) );
}
}
IntStream
.range( 0, columns.size() )
.forEach( col -> columns.get(col).addWord(line[col]) )
;
return this;
}
public Table toPivot(){
Table result = new Table();
for(Column column : columns){
result.addLine( column.getWords() );
}
result
.separateColumnsWith(columnSeparator)
.padWith(pad)
.boxWith(boxCharacters)
.boxType(boarder)
.outputWith(out)
.alignLeft()
;
return result;
}
// .---{ Boxing }---. //
public Table box() {
boarder = BoxType.BOX;
return this;
}
public Table boxOff() {
boarder = BoxType.BOXOFF;
return this;
}
public Table boxHeader() {
boarder = BoxType.HEAD;
return this;
}
private Table boxType(BoxType boarder){
this.boarder = boarder;
return this;
}
// '---{ Boxing }---' //
public void print(){
out.println( toString() );
}
public String toString(){
final int rows = columns.get(0).size();
return new Table.Bound().box(
boarder,
IntStream
.range(0, rows)
.mapToObj(row -> columns
.stream()
.map( col -> col.getCell(row) )
.collect( Collectors.joining( columnSeparator ) )
)
.collect( Collectors.joining( System.lineSeparator() ) )
);
}
// .--{ Make defaults overridable }--. //
public Table outputWith(PrintStream out){
this.out = out;
return this;
}
public Table separateColumnsWith(String columnSeparator){
this.columnSeparator = columnSeparator;
return this;
}
public Table padWith(char pad){
this.pad = pad;
return this;
}
public Table boxWith(String box){
this.boxCharacters = box;
return this;
}
// '--{ Make defaults overridable }--' //
public Table alignLeft(){
alignment = LEFT;
columns.forEach(col -> col.setAlignment(alignment));
return this;
}
public Table alignCenter(){
alignment = CENTER;
columns.forEach(col -> col.setAlignment(alignment));
return this;
}
public Table alignRight(){
alignment = RIGHT;
columns.forEach(col -> col.setAlignment(alignment));
return this;
}
public Table alignLeft(int col){
columns.get(col).setAlignment( LEFT );
return this;
}
public Table alignCenter(int col){
columns.get(col).setAlignment( CENTER );
return this;
}
public Table alignRight(int col){
columns.get(col).setAlignment( RIGHT );
return this;
}
public Table alignLeft(int col, int cell){
columns.get(col).setAlignment( LEFT, cell );
return this;
}
public Table alignCenter(int col, int cell){
columns.get(col).setAlignment( CENTER, cell );
return this;
}
public Table alignRight(int col, int cell){
columns.get(col).setAlignment( RIGHT, cell );
return this;
}
@FunctionalInterface
interface Alignment {
String align(String left, String word, String right);
}
enum BoxType {
BOXOFF(Table.Bound::boxOff),
BOX(Table.Bound::box),
HEAD(Table.Bound::boxHeader);
BiFunction<Table.Bound, String, String> boxLogic;
BoxType(BiFunction<Table.Bound, String, String> boxLogic){
this.boxLogic = boxLogic;
}
}
class Bound {
private String
cornerTL,
top,
cornerTR,
sideLH,
middleH,
sideRH,
sideL,
middle,
sideR,
cornerBL,
bottom,
cornerBR
;
{
setBoxCharacters( boxCharacters );
}
public void setBoxCharacters(String boxCharacters) {
if (boxCharacters.length() != 12) {
throw new IllegalArgumentException("box characters string unsupported size");
}
cornerTL = boxCharacters.substring(0,1);
top = boxCharacters.substring(1,2);
cornerTR = boxCharacters.substring(2,3);
sideLH = boxCharacters.substring(3,4);
middleH = boxCharacters.substring(4,5);
sideRH = boxCharacters.substring(5,6);
sideL = boxCharacters.substring(6,7);
middle = boxCharacters.substring(7,8);
sideR = boxCharacters.substring(8,9);
cornerBL = boxCharacters.substring(9,10);
bottom = boxCharacters.substring(10,11);
cornerBR = boxCharacters.substring(11,12);
}
public String box(BoxType boxType, String body){
return boxType.boxLogic.apply(this, body);
}
public String boxOff(String body){
return body;
}
public String box(String body){
final int width = body.indexOf(System.lineSeparator());
// One Two Three Four
// 1 2 3 4
// 5 6 7 8
body = body.replace(System.lineSeparator(), sideR + System.lineSeparator() + sideL);
// One Two Three Four|
// |1 2 3 4 |
// |5 6 7 8
body =
cornerTL + top.repeat(width) + cornerTR + System.lineSeparator() +
sideL + body + sideR + System.lineSeparator() +
cornerBL + bottom.repeat(width) + cornerBR
;
// .------------------.
// |One Two Three Four|
// |1 2 3 4 |
// |5 6 7 8 |
// '------------------'
return body;
}
public String boxHeader(String body){
final int width = body.indexOf(System.lineSeparator());
// One Two Three Four
// 1 2 3 4
// 5 6 7 8
body = body.replaceFirst(
System.lineSeparator(),
System.lineSeparator() + middleH.repeat(width) + System.lineSeparator()
);
// One Two Three Four
// ------------------
// 1 2 3 4
// 5 6 7 8
body = box(body);
// .------------------.
// |One Two Three Four|
// |------------------|
// |1 2 3 4 |
// |5 6 7 8 |
// '------------------'
//Replace header sides
final int ls = System.lineSeparator().length();
final int lineLength = 1+width+1+ls;
body =
body.substring( 0, 2*lineLength ) +
sideLH + middleH.repeat(width) + sideRH + System.lineSeparator() +
body.substring( 3*lineLength, body.length() )
;
// .------------------.
// |One Two Three Four|
// }------------------{
// |1 2 3 4 |
// |5 6 7 8 |
// '------------------'
return body;
}
}
class Column {
private List<Cell> words = new ArrayList<>();
private int maxLength = 0;
private Table.Alignment alignment;
public Column(Table.Alignment alignment){
setAlignment(alignment);
}
public Table.Column addWord(String word){
maxLength = Math.max(maxLength, word.length());
words.add( new Cell(word) );
return this;
}
public String getCell(int row){
return words.get(row).padCell( maxLength );
}
public String[] getWords(){
return words
.stream()
.map( cell -> cell.getWord() )
.toArray(size -> new String[size])
;
}
public int size(){
return words.size();
}
public String toString(){
return words.toString();
}
public Column setAlignment(Table.Alignment alignment){
this.alignment = alignment;
words.forEach(cell -> cell.setAlignment(alignment));
return this;
}
public Column setAlignment(Table.Alignment alignment, int cell){
words.get(cell).setAlignment(alignment);
return this;
}
class Cell {
private String word;
private Table.Alignment alignment;
public Cell(String word){
this.word = word;
}
public String getWord(){
return word;
}
public void setAlignment(Table.Alignment alignment){
this.alignment = alignment;
}
public String toString(){
return padCell(maxLength);
}
private String padCell(int newLength){
final String padding = "" + pad;
int padCount = newLength - word.length();
int leftCount = padCount / 2;
int rightCount = padCount - leftCount;
String left = padding.repeat(leftCount);
String right = padding.repeat(rightCount);
return alignment.align(left, word, right);
}
}
}
}
Back on my 4th attempt I got many wonderful reviews that inspired changes that made it into my 5th attempt. One idea from the review by @Roman was to pivot (columns become rows). This is my attempt to answer that review.
In addition to adding toPivot()
I also changed pad
from a String to a char. Made copying it's value from the old table easier. Thoughts most welcome.