2011-09-24 103 views
11

我需要在控制檯中顯示一個表格。Scala:繪製表格到控制檯

我簡單的解決方案,如果你把它叫做一個「解決方案」,如下:

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 

    } 

我們不必爭辯這個

  • 顯示的
  • 時面色不善
  • b碼看起來有點搞砸

其實,這樣的問題被問了C#,但我想也知道Scala的一個很好的解決方案。

那麼什麼是(好/好/簡單/無論)的方式來繪製在Scala這樣的表到控制檯?

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

回答

24

我拉着從我目前的項目如下:

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

這是美麗的。嘗試過它,正是我一直在尋找的東西。 – evildead

+4

作爲一個添加這是左alignement(「%」+大小+「s」)。格式(項目)這個權利(「% - 」+大小+「s」)。格式(項目) – evildead

+1

會更好作爲'隱式類',增加例如例如「.asTable」 'Seq [Seq [Any]]':) –

2

Tokenize it。我會用在尋找做一些案例的對象和類開始,讓你產生一種符號化列表可用於顯示的操作上:

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 
} 

,那麼你可以與某種行對象的某些限制:

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

然後,您可以以不可變的方式檢查約束和類似事情。也許創建方法追加表和對齊......

case class Table(contents: List[TableTokens]) 

這意味着你可以有表的多個不同變體,其中你的風格是從你的結構,一拉HTML和CSS不同。

+0

也許你可以添加一個小例子。我沒有完全掌握它。 – evildead

+0

我第二@ evildead - 爲什麼要標記任何東西,如果這是關於渲染現有的序列? –

2

噸感謝製表代碼!

Spark數據集表格打印有修改。

我的意思是,你可以打印數據幀的內容或拉的結果集,就像

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

第二個將不當然的頭,對DF實現你可以設置多少行從星火數據幀扳爲打印和你需要頭或不。

/** 
* 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("+", "+", "+") 

}