文档章节

Scala - scala examples : Scala spreasheet

j
 joe.bq.wang
发布于 2013/07/07 11:02
字数 1885
阅读 430
收藏 3

In this post, we will check an example that create a SCells Spreadsheet. 

So we first check on the visual framework. 

The Visual framework

extends the ScrollPane, which gives it a Scroll-bars at the bottom and right. It contains two sub-component named Table and rowHeader.
Table display the data, while the rowHeader component contains the row-number headers at the left of the spreadsheet. 
the GUI code that forms the skeleton is as follow. 

// the code as below 
package org.stairwaybook.scelles

import swing._

class Spreadsheet(val height : Int, val width : Int) 
	extends ScrollPane { 
  val table = new Table(height, width) {
    rowHeight = 25
    autoResizeMode = Table.AutoResizeMode.Off
    showGrid = true
    gridColor = new java.awt.Color(150, 150, 150)
  }
  
  val rowHeader = 
    new ListView((0 until height) map (_.toString)) {
      fixedCellWidth = 30
      fixedCellHeight = table.rowHeight
  }
  
  viewportView = table
  rowHeaderView = rowHeader
}
To try this rudimentary spreadsheet out, here is the main class. 

// the code as below 
package org.stairwaybook.scelles

import swing._

object Main extends SimpleGUIApplication { 
  def top = new MainFrame { 
    title  = "ScalaSheet"
    contents = new Spreadsheet(100, 20)
  }
}

Disconnectng data entry and display 

A good UI code, may have demands that the code separation between UI and model. This is the same in Scala as well in elsewhere. 

First, we need to refactor the UI code, we want to show the contents with meaning value when it is beeing edited or when it is just display the result. so we will use the renderedComponent method, it is an override method, and it works as follow. 

// the code as below 
package org.stairwaybook.scelles

import swing._

class Spreadsheet(val height : Int, val width : Int) 
	extends ScrollPane { 
  // the data
  val cellMode = new Model(height, width)
  import cellMode._
  
  
  val table = new Table(height, width) {
    rowHeight = 25
    autoResizeMode = Table.AutoResizeMode.Off
    showGrid = true
    gridColor = new java.awt.Color(150, 150, 150)
    
    override def rendererComponent(isSelected : Boolean, hasFocus : Boolean, row : Int, column : Int) : Component = {
	    if (hasFocus) new TextField(userData(row, column))
	    else 
	      new Label(cells(row)(column).toString) {
	      xAlignment = Alignment.Right
	    }
	}
	  
	  def userData(row : Int, column : Int) : String = { 
	    val v = this(row, column)
	    if (v == null) "" else v.toString
	}
  
  }
  
  val rowHeader = 
    new ListView((0 until height) map (_.toString)) {
      fixedCellWidth = 30
      fixedCellHeight = table.rowHeight
  }
  
  viewportView = table
  rowHeaderView = rowHeader
}
as we see that we uses userData, and that uses cells from the Model class. here is the code of the Model class. 

package org.stairwaybook.scelles

import swing._

class Model(val height : Int, val width: Int) {
  case class Cell(row :Int, column : Int)
  
  val cells = new Array[Array[Cell]](height, width)
  
  for (i <- 0 until height; j <- 0 until width) 
    cells(i)(j) = new Cell(i, j)
}
now we have basic Spreadsheet, but it does not allow us to do useful things such as computation, it just display string as you hasve inputed it. 

Formulas

In reality , a spread sheet cell holds two things : An actual value and a formula to compute this value. there are three types of formulas in a spreadsheets

1. Numeric  value such as 1.22, -3, or 0 
2. Textual value such as Annual sales, Deprecation, or total
3. Formula that compute a new value from the content of cells, such as "=add(A1, B2)" or "=sum((mul(2, A2), C1: D16)"

Coord       for cell coordinates such as A3
Range       for cell ranges such as A3:B17
Number      for floating-point numbers such as 3.1415
Textual     for textual labels such as Deprecation
Application for function applications such as sum(A1, A2).


so we have defined the following Formula classes. 

package org.stairwaybook.scelles

trait Formula

case class Coord(row :Int, column : Int) extends Formula {
  override def toString = ('A' + column).toChar .toString + row
}

case class Range(c1 : Coord, c2: Coord) extends Formula { 
  override def toString = c1.toString + ":" + c2.toString
}

case class Number(value : Double) extends Formula { 
  override def toString = value.toString
}

case class Textual(value : String) extends Formula { 
  override def toString = value
}

// stands for a formula which should have a function name and a list of arguments. 
case class Application(function: String, arguments : List[Formula]) extends Formula {
  override def toString = function + arguments.mkString("(", ",", ")")
}

object Empty extends Textual("")

Parsing formulas 

we need to have a way to parse the formula into a formula tree. 

import scala.util.parsing.combinator._

object FormulaParsers extends RegexParsers { 
  def ident : Parser[String] = """[a-zA-Z_]\w*""".r
  def decimal : Parser[String] = """-?\d+(\.\d*)?""".r
  // next, we will need to capture Cell
  def cell : Parser[Coord] = 
    """[a-zA-Z_]\d+""".r ^^ { s =>  
    val column = s.charAt(0).toUpper - 'A'
    val row = s.substring(1).toInt
    Coord(row, column)
  }
  
  def range: Parser[Range] = 
    cell ~":"~cell ^^ { 
    case c1~":"~c2 => Range(c1, c2)
  }
  
  def number : Parser[Number] = 
    decimal ^^ (d => Number(d.toDouble))
    
  def application : Parser[Application] = 
    ident ~ "(" ~ repsep(expr, ",") ~ ")" ^^ { 
      case f ~ "(" ~ ps ~ ")" => Application(f, ps)
    }
 
  def expr : Parser[Formula] = 
    range | cell | number | application 
    
  def textual : Parser[Textual] = 
    """[^=].*""".r ^^ Textual // this is very neat... , you can directly apply the Textual (as Function1) on the ParserReult[T] on the Textual case class
  
  def formula : Parser[Formula] = 
    number | textual | "=" ~> expr
    
  def parse(input  : String ) : Formula = 
    parseAll(formula, input) match { 
      case Success (e, _) => e
      case f : NoSuccess => Textual("[" + f.msg + "]")
    }
} // end FormulaParsers
this is using the Combinator framework . 

To support the formula on Cells, here is the new version of Model , which has the formula members. 

import swing._

class Model(val height : Int, val width: Int) {
  case class Cell(row :Int, column : Int) {
    var formula : Formula = Empty 
    override def toString = formula.toString
  }
  
  val cells = new Array[Array[Cell]](height, width)
  
  for (i <- 0 until height; j <- 0 until width) 
    cells(i)(j) = new Cell(i, j)
}
now with the Formula, we still display the string just as the formula's string representation. 

Evaluation 

we create a new Evaluation trait, which has the evaluate method. 

trait Evaluator { this : Model => 
  def evaluate(e: Formula) : Double = try { 
    e match { 
      case Coord(row, column) => 
        cells(row)(column).value 
      case Number(v) => 
        v
      case Textual(_) => 
        0
      case Application(function, arguments) => 
        val argvals = arguments flatMap evalList 
        operations(function)(argvals)
    }
  } catch {
    case ex : Exception => Double.NaN
  }

  type Op = List[Double] => Double
  val operations = new collection.mutable.HashMap[String, Op]
  
  private def evalList(e: Formula) : List[Double] = e match { 
    case Range(_, _) => references(e) map (_.value)
    case _ => List(evaluate(e)) // the recursion goes here
  }
  
  def references(e : Formula) : List[Cell] = e match { 
    case Coord (row, column) => 
      List(cells(row)(column))
    case Range(Coord(r1, c1), Coord(r2, c2)) => 
      for (row <- (r1 to r2).toList; column <- c1 to c2)
        yield cells(row)(column)
    case Application(function, arguments) =>
      arguments flatMap references
    case _ => 
      List()
  }
} // end Evaluator
the key is the Application ,which calles operations, which is a map from function name to the function's application. 

type Op = List[Double] => Double
  val operations = new collection.mutable.HashMap[String, Op]
and to support evaluation on nested arguments, we have evalList. 

private def evalList(e: Formula) : List[Double] = e match { 
    case Range(_, _) => references(e) map (_.value)
    case _ => List(evaluate(e)) // the recursion goes here
  }
and in order to support argument such as [C1:C5] or C1 and others types, we define a method references , which can turn a formula classes into a cell references. as follow. 

def references(e : Formula) : List[Cell] = e match { 
    case Coord (row, column) => 
      List(cells(row)(column))
    case Range(Coord(r1, c1), Coord(r2, c2)) => 
      for (row <- (r1 to r2).toList; column <- c1 to c2)
        yield cells(row)(column)
    case Application(function, arguments) =>
      arguments flatMap references
    case _ => 
      List()
  }

equally, when we 

Operations libraries

To actually convert the formula into the operations, so we can really convert to real calculation. 

// the trait populate the operations table during its initialization. 
trait Arithmetic { this : Evaluator => 
    operations += (
      "add" -> { case List(x, y) => x + y },
      "sub" -> { case List(x, y) => x - y },
      "div" -> { case List(x, y) => x / y },
      "mul" -> { case List(x, y) => x * y },
      "mod" -> { case List(x, y) => x % y },
      "sum" -> { xs => (0.0 /: xs) (_ + _) },
      "prod" -> { xs => (1.0 /: xs) (_ * _) }
    )
}
this trait does not export any exported members, we need to revise the Model class, here ist he model clases that introduced the Formula. 

To support evaluation, Model now mix in Evaluator trait to support evaluate 

import swing._

class Model(val height : Int, val width: Int) 
      extends Evaluator with Arithmetic {
  case class Cell(row :Int, column : Int) {
    var formula : Formula = Empty 
    
    def value = evaluate(formula)
    
    override def toString = formula match { 
      case Textual(s) => s
      case _ => value.toString
    }
  }
  
  val cells = new Array[Array[Cell]](height, width)
  
  for (i <- 0 until height; j <- 0 until width) 
    cells(i)(j) = new Cell(i, j)
}

Change Propagation 

so we have all the meat and potatos, but we don't have change propagations, if one of hte dependent cell changes, we are not being informed and the cell is not updated. 

we need to extend the Model class. This we extend our Cell case class to let it extend from the Swing.event.Publisher class and we redefine the formula class to let it have the subscription and unsubcription wired up . as follow. 

class Model(val height : Int, val width: Int) 
      extends Evaluator with Arithmetic {
 
  case class Cell(row :Int, column : Int) extends Publisher {
    private var f : Formula = Empty
    def formula_=(f : Formula) { 
      
      for (c <- references(formula)) deafTo(c)
      this.f = f
      for (c <- references(formula)) listenTo(c)
      value = evaluate(f)
    }
    def formula : Formula = f 
    
    private var v : Double = 0
    def value = evaluate(formula)
    def value_= (w: Double) {
      if (!(v == w || v.isNaN && w.isNaN)) {
        v = w
        publish(ValueChanged(this))
      }
    }
    
    override def toString = formula match { 
      case Textual(s) => s
      case _ => value.toString
    }
    
    // reactions is from the Publisher classes
    reactions += {
      case ValueChanged(_) => value = evaluate(formula)
    }
  } // end class Cell
  
  case class ValueChanged(cell : Cell) extends event.Event
  
  val cells = new Array[Array[Cell]](height, width)
  
  for (i <- 0 until height; j <- 0 until width) 
    cells(i)(j) = new Cell(i, j)
}
in this code, we define a new event called ValueChanged, when a cell is given a different value, the event will be fired. and we need as well to handle the ValueChanged event (this is because some upstream cells when it call publich(ValueChanged(this)), the downstream should react and reevaluate the formula. 

Now, the cell has changed, we need as well to extend the Spreadsheet class to 1. listen to all cells 2. create a handler for the ValueChanged event to redraw content for the cell. the key is to call cellUpdate(row, cell). here is the new Spreadsheet class. 

import swing._
import swing.event._

class Spreadsheet(val height : Int, val width : Int) 
	extends ScrollPane { 
  // the data
  val cellMode = new Model(height, width)
  import cellMode._
  
  
  val table = new Table(height, width) {
    rowHeight = 25
    autoResizeMode = Table.AutoResizeMode.Off
    showGrid = true
    gridColor = new java.awt.Color(150, 150, 150)
    
    override def rendererComponent(isSelected : Boolean, hasFocus : Boolean, row : Int, column : Int) : Component = {
	    if (hasFocus) new TextField(userData(row, column))
	    else 
	      new Label(cells(row)(column).toString) {
	      xAlignment = Alignment.Right
	    }
	}
	  
	def userData(row : Int, column : Int) : String = { 
	    val v = this(row, column)
	    if (v == null) "" else v.toString
	}
	  
	reactions += {
	  case TableUpdated(table, rows, column) => 
	    for (row <- rows) 
	      cells(row)(column).formula = FormulaParsers.parse(userData(row, column))
	  case ValueChanged(cell) => 
	    updateCell(cell.row, cell.column) // redraw the cell with the updateCell call 
	}
	
	for (row <- cells; cell <- row) listenTo(cell) // wire up and listen to all the cells 
  
  }
  
  val rowHeader = 
    new ListView((0 until height) map (_.toString)) {
      fixedCellWidth = 30
      fixedCellHeight = table.rowHeight
  }
  
  viewportView = table
  rowHeaderView = rowHeader
}

The main class

To run the code, we need a Main class, it is as follow. 

import swing._

object Main extends SimpleGUIApplication { 
  def top = new MainFrame { 
    title  = "ScalaSheet"
    contents = new Spreadsheet(100, 20)
  }
}
run the following code 
scala Main

and if you have the package statement above, you should run it like this:
scala org.stairwaybook.scelles.Main
or just 

Main.main(Array[String]())
if you are running from the Eclipse interactive shel. 

© 著作权归作者所有

j
粉丝 10
博文 183
码字总数 80491
作品 0
浦东
私信 提问
Scala开发问题汇总(不断更新中)

1、Scala教程 Scala Tutorial 2、Java和Scala集合如何进行互转? CONVERSIONS BETWEEN JAVA AND SCALA COLLECTIONS Converting Java collections to Scala 3、如何将List转换为Map? Conver......

九州暮云
2018/07/05
34
0
Mxnet Scala Package 学习笔记 一

前言 从刚开始接触Mxnet这个框架到现在已经大概四个月了。Mxnet最吸引我的地方就是它提供了 很多语言的接口,其中有Scala(my favorite),这是我从Caffe转过来的原因之一。 Mxnet是我第一个...

Ldpe2G
2016/06/23
489
2
Spark 学习资源收集

(一)spark 相关安装部署、开发环境 1、Spark 伪分布式 & 全分布式 安装指南 http://my.oschina.net/leejun2005/blog/394928 2、Apache Spark探秘:三种分布式部署方式比较 http://dongxic...

openthings
2016/05/29
201
0
【Spark】Spark SQL, DataFrames and Datasets Guide(翻译文,持续更新)

本文主要是翻译Spark官网Spark SQL programming guide 。只能保证大概意思,尽量保证细节。英文水平有限,如果有错误的地方请指正,轻喷。目录导航在右上角 Spark SQL、DataFrames 和 Datase...

跑呀跑
2018/09/19
0
0
Spark机器学习(三) Labeled point-- Data Types

Labeled point A labeled point is a local vector, either dense or sparse, associated with a label/response. In MLlib, labeled points are used in supervised learning algorithms. W......

snug
2016/04/23
3.2K
0

没有更多内容

加载失败,请刷新页面

加载更多

最简单的获取相机拍照的图片

  import android.content.Intent;import android.graphics.Bitmap;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import andr......

MrLins
今天
4
0
说好不哭!数据可视化深度干货,前端开发下一个涨薪点在这里~

随着互联网在各行各业的影响不断深入,数据规模越来越大,各企业也越来越重视数据的价值。作为一家专业的数据智能公司,个推从消息推送服务起家,经过多年的持续耕耘,积累沉淀了海量数据,在...

个推
今天
8
0
第三方支付-返回与回调注意事项

不管是支付宝,微信,还是其它第三方支付,第四方支付,支付机构服务商只要涉及到钱的交易都要进行如下校验,全部成功了才视为成功订单 1.http请求是否成功 2.校验商户号 3.校验订单号及状态...

Shingfi
今天
4
0
简述Java内存分配和回收策略以及Minor GC 和 Major GC(Full GC)

内存分配: 1. 栈区:栈可分为Java虚拟机和本地方法栈 2. 堆区:堆被所有线程共享,在虚拟机启动时创建,是唯一的目的是存放对象实例,是gc的主要区域。通常可分为两个区块年轻代和年老代。更...

DustinChan
今天
6
0
Excel插入批注:可在批注插入文字、形状、图片

1.批注一直显示:审阅选项卡-------->勾选显示批注选项: 2.插入批注快捷键:Shift+F2 组合键 3.在批注中插入图片:鼠标右键点击批注框的小圆点【重点不可以在批注文本框内点击】----->调出批...

东方墨天
今天
6
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部