2011-09-24 14 views
11

Devo visualizzare una tabella in una console.Scala: Disegna tabella su console

mia soluzione semplice, se volete chiamarla una "soluzione", è la seguente:

override def toString() = { 
    var res = "\n" 
     var counter = 1; 
     res += stateDb._1 + "\n" 
     res += " +----------------------------+\n" 
     res += " +  State Table   +\n" 
     res += " +----------------------------+\n" 
     for (entry <- stateDb._2) { 
     res += " | " + counter + "\t | " + entry._1 + " | " + entry._2 + " |\n" 
     counter += 1; 
     } 
     res += " +----------------------------+\n" 
     res += "\n" 
    res 

    } 

Non abbiamo di discutere questo

  • sta cercando un male quando visualizzata
  • codice B sembra un pò incasinato

in realtà, una simile domanda è stato chiesto per C#, ma vorrei per conoscere anche una bella soluzione per Scala.

Quindi, qual è un modo (bello/buono/semplice/qualsiasi) di disegnare un tale tavolo in Scala sulla console?

------------------------------------------------------------------------- 
| Column 1  | Column 2  | Column 3  | Column 4  | 
------------------------------------------------------------------------- 
|     |     |     |     | 
|     |     |     |     | 
|     |     |     |     | 
------------------------------------------------------------------------- 

risposta

24

ho tirato il seguente dal mio progetto in corso:

object Tabulator { 
    def format(table: Seq[Seq[Any]]) = table match { 
    case Seq() => "" 
    case _ => 
     val sizes = for (row <- table) yield (for (cell <- row) yield if (cell == null) 0 else cell.toString.length) 
     val colSizes = for (col <- sizes.transpose) yield col.max 
     val rows = for (row <- table) yield formatRow(row, colSizes) 
     formatRows(rowSeparator(colSizes), rows) 
    } 

    def formatRows(rowSeparator: String, rows: Seq[String]): String = (
    rowSeparator :: 
    rows.head :: 
    rowSeparator :: 
    rows.tail.toList ::: 
    rowSeparator :: 
    List()).mkString("\n") 

    def formatRow(row: Seq[Any], colSizes: Seq[Int]) = { 
    val cells = (for ((item, size) <- row.zip(colSizes)) yield if (size == 0) "" else ("%" + size + "s").format(item)) 
    cells.mkString("|", "|", "|") 
    } 

    def rowSeparator(colSizes: Seq[Int]) = colSizes map { "-" * _ } mkString("+", "+", "+") 
} 

scala> Tabulator.format(List(List("head1", "head2", "head3"), List("one", "two", "three"), List("four", "five", "six"))) 
res1: java.lang.String = 
+-----+-----+-----+ 
|head1|head2|head3| 
+-----+-----+-----+ 
| one| two|three| 
| four| five| six| 
+-----+-----+-----+ 
+0

questo è bellissimo. Provato ed era esattamente quello che stavo cercando. – evildead

+4

come aggiunta questo è lasciato allineamento ("%" + taglia + "s"). Formato (oggetto) questo diritto ("% -" + taglia + "s"). Formato (articolo) – evildead

+1

Sarebbe ancora più bello come 'classe implicita' che aggiunge ad es ". astable" ad es. 'Seq [Seq [Any]]' :) –

2

Tokenize it. Mi piacerebbe iniziare con guardando fare un paio di oggetti di casi e le classi in modo da produrre un elenco token che può essere azionato in per la visualizzazione:

sealed trait TableTokens{ 
    val width: Int 
} 
case class Entry(value: String) extends TableTokens{ 
    val width = value.length 
} 
case object LineBreak extends TableTokens{ 
    val width = 0 
} 
case object Div extends TableTokens{ 
    val width = 1 
} 

Così allora si può formare alcuni vincoli con una sorta di oggetto riga :

case class Row(contents: List[TableTokens]) extends TableTokens{ 
    val width = contents.foldLeft(0)((x,y) => x = y.width) 
} 

Quindi è possibile verificare i vincoli e cose del genere in modo immutabile. Forse la creazione di metodi per le tabelle allegate e l'allineamento ...

case class Table(contents: List[TableTokens]) 

Ciò significa che potrebbe avere diverse varianti di tavoli dove il vostro stile è diverso dalla struttura, alla HTML e CSS.

+0

forse si può aggiungere un po 'di esempio. Non lo sto facendo completamente. – evildead

+0

I second @ evildead: perché abilitare tokenize se si tratta di eseguire il rendering di una sequenza esistente? –

2

Ton di ringraziamento per il codice Tabulator!

C'è una modifica per Spark stampa tabellare dati.

io significa che si può stampare il contenuto dataframe o set di risultati tirato, come

Tabulator(hiveContext.sql("SELECT * FROM stat")) 
Tabulator(hiveContext.sql("SELECT * FROM stat").take(20)) 

Il secondo sarà senza intestazione, naturalmente, per l'attuazione DF è possibile impostare il numero di righe per tirare dal frame di dati Spark per stampa e hai bisogno di intestazione o meno.

/** 
* Tabular representation of Spark dataset. 
* Usage: 
* 1. Import source to spark-shell: 
* spark-shell.cmd --master local[2] --packages com.databricks:spark-csv_2.10:1.3.0 -i /path/to/Tabulator.scala 
* 2. Tabulator usage: 
* import org.apache.spark.sql.hive.HiveContext 
* val hiveContext = new HiveContext(sc) 
* val stat = hiveContext.read.format("com.databricks.spark.csv").option("header", "true").option("inferSchema", "true").option("delimiter", "\t").load("D:\\data\\stats-belablotski.tsv") 
* stat.registerTempTable("stat") 
* Tabulator(hiveContext.sql("SELECT * FROM stat").take(20)) 
* Tabulator(hiveContext.sql("SELECT * FROM stat")) 
*/ 
object Tabulator { 

    def format(table: Seq[Seq[Any]], isHeaderNeeded: Boolean) : String = table match { 
    case Seq() => "" 
    case _ => 
     val sizes = for (row <- table) yield (for (cell <- row) yield if (cell == null) 0 else cell.toString.length) 
     val colSizes = for (col <- sizes.transpose) yield col.max 
     val rows = for (row <- table) yield formatRow(row, colSizes) 
     formatRows(rowSeparator(colSizes), rows, isHeaderNeeded) 
    } 

    def formatRes(table: Array[org.apache.spark.sql.Row]): String = { 
    val res: Seq[Seq[Any]] = (for { r <- table } yield r.toSeq).toSeq 
    format(res, false) 
    } 

    def formatDf(df: org.apache.spark.sql.DataFrame, n: Int = 20, isHeaderNeeded: Boolean = true): String = { 
    val res: Seq[Seq[Any]] = (for { r <- df.take(n) } yield r.toSeq).toSeq 
    format(List(df.schema.map(_.name).toSeq) ++ res, isHeaderNeeded) 
    } 

    def apply(table: Array[org.apache.spark.sql.Row]): Unit = 
    println(formatRes(table)) 

    /** 
    * Print DataFrame in a formatted manner. 
    * @param df Data frame 
    * @param n How many row to take for tabular printing 
    */ 
    def apply(df: org.apache.spark.sql.DataFrame, n: Int = 20, isHeaderNeeded: Boolean = true): Unit = 
    println(formatDf(df, n, isHeaderNeeded)) 

    def formatRows(rowSeparator: String, rows: Seq[String], isHeaderNeeded: Boolean): String = (
    rowSeparator :: 
    (rows.head + { if (isHeaderNeeded) "\n" + rowSeparator else "" }) :: 
    rows.tail.toList ::: 
    rowSeparator :: 
    List()).mkString("\n") 

    def formatRow(row: Seq[Any], colSizes: Seq[Int]) = { 
    val cells = (for ((item, size) <- row.zip(colSizes)) yield if (size == 0) "" else ("%" + size + "s").format(item)) 
    cells.mkString("|", "|", "|") 
    } 

    def rowSeparator(colSizes: Seq[Int]) = colSizes map { "-" * _ } mkString("+", "+", "+") 

}