CIT 594 List Preparer in Scala
Spring 2015, David Matuszek

This page goes along with assignment 11, Flash Card Merge.

The notes on the right refer to line numbers on the left. I cannot make these line up accurately for every possible screen size. I recommend using your full screen for this page, and possibly also reducing the font size.

package listPreparation

object Item {
  var errors = false
  
  def apply(line: String) = {
    val parts = line.split(" *\\|\\| *")
    parts match {
      case Array(a, b, c, d, e, f) =>
        new Item(a, b, c.toInt, d.toInt, e.toInt, f.toInt)
      case Array(a, b) =>
        new Item(a, b)
      case _ =>
        errors = true
        new Item("// " + line, "?", 0, 0, 0, 0)
    }
  }
}

case class Item(val stimulus: String, var response: String,
                timesCorrect: Int, timesIncorrect: Int,
                interval: Int, date: Int) {
  
  /** Constructor for virgin items. */
  def this(stimulus: String, response: String) {
    this(stimulus, response, 0, 0, 0, 0)
  }
  
  def equalStimulus(that: Item) = this.stimulus == that.stimulus
  
  def equalResponse(that: Item) = this.response == that.response
  
  override def toString =
    stimulus + " || " + response +
    (if (timesCorrect != 0 || timesIncorrect != 0)
       " || " + timesCorrect + " || " + timesIncorrect + 
       " || " + interval + " || " + date
     else "")
}

 

3. This defines a singleton object of class Item. Variable definitions (line 4) and function definitions (line 6) in this object are like static definitions in Java.

 

 

6. apply constructs and returns a new Item from the given line.

 

 

8. The parts array is tested to see whether it has 6 elements (line 9), 2 elements (line 11), or something else (line 13).

 

 

20-23. The case class is both a definition of class Item and a constructor for it. It provides getter methods for val parameters and getter and setter methods for var parameters.

 

 

25. this defines an auxiliary constructor.

 

 

29. Testing string equality with == just works.





34. The last value computed in a function is the value that it returns.

 

35. if...else is an expression as well as a statement.

package listPreparation

import java.io.File

object ListPreparer {

  def main(args: Array[String]): Unit = {
    val itemList1 = readItemList(None)    
    val itemList2 = readItemList(None)
    val itemList3 = combineItemLists(itemList1, itemList2)
    writeItemList(itemList3)
  }
  
  /** Choose a file and read it in as a list of lines. */
  def readFileAsListOfLines(): List[String] = {
    import scala.swing.FileChooser, java.io.File
      val chooser = new FileChooser
      val response = chooser.showOpenDialog(null)
      if (response == FileChooser.Result.Approve) {
        readFileAsListOfLines(chooser.selectedFile)
      }
      else readFileAsListOfLines()
  }
  
  /** Read in the named file as a list of lines. */
  def readFileAsListOfLines(fileName: String): List[String] = {
    val file = new File(fileName)
    readFileAsListOfLines(file)
  }
  
  /* Read in the File as a list of lines. */
  def readFileAsListOfLines(file: File): List[String] = {
    import scala.io.Source, scala.io.Source._
    val stream = Source.fromFile(file)
    val lines = stream.getLines.toList
    stream.close
    lines
  }
  
  /** Given a File, file name, or None, read in a list of Items. */
  def readItemList(source: Any): List[Item] = {
    val lines = source match {
      case fileName: String => readFileAsListOfLines(fileName)
      case file: File => readFileAsListOfLines(file)
      case _ => readFileAsListOfLines()
    }
    for (line <- lines) yield Item(line)
  }
  
  /** Choose a file and save the given item list on it. */
  def writeItemList(list: List[Item]) {
    import scala.swing.FileChooser, java.io.File, java.io.PrintWriter
    val chooser = new FileChooser
    val response = chooser.showSaveDialog(null)
    if (response == FileChooser.Result.Approve) {
      val stream = new PrintWriter(chooser.selectedFile)
      for (item <- list) stream.println(item)
      stream.close
    }
  }
  
  /** Find stimulus stimulus in item list. */
  def findStimulus(goal: Item, list: List[Item]): Option[Item] = {
    for (item <- list if item.equalStimulus(goal)) return Some(item)
    return None
  }
  
  /** Start with list1 and add items from list2. If the stimulus from
   *  list2 is the same as from list1, but the response differs, ask
   *  the user to choose between them. A response other than 1 or 2
   *  is taken to be a new definition. */
  def combineItemLists(list1: List[Item], list2: List[Item]): List[Item] = {
    var list3 = for (item <- list1) yield item
    for (item2 <- list2) {
      findStimulus(item2, list1) match {
        case Some(item1) => 
          if (! item2.equalResponse(item1)) {
            println("1 " + item1.stimulus + " || " + item1.response)
            println("2 " + item2.stimulus + " || " + item2.response)
            readLine("Which? ").trim match {
              case "1" => // Don't change item
              case "2" => item1.response = item2.response
              case newDef => item1.response = newDef
            }
          }
        case None => list3 ++= List(item2)
      }
    }
    list3
  }
}

 

5. We only need one ListPreparer object, so we don't need to define a separate class.

 


15, 26, 32. Overloaded methods. Not all are in current use, but they are being kept for reasons of flexibility. The return type is specified after the colon (:).

 


16, 33, 52. import statements can be local.


 

42. An appropriate case is chosen based on the type of the parameter source.

 


47. Converts a list of lines into a list of Items.

 


65. Where a Java method might return an object or null, Scala prefers to return a Some(object) or None.


 

83. In this case, the variable newDef is set to the value read in by readLine.

 

86. item2 is appended to list3.