I've tried to create optimal in terms of performance and memmory consumption. But also I've tried to make it functional and scala way. I want to get you comment on how to make it more 'scala'stic
object LifeGame extends App {
trait Matrix extends Iterable[(Int, Int)] {
def apply(p: (Int, Int)): Boolean
def update(p: (Int, Int), b: Boolean): Unit
def newInstance: Matrix;
}
class SparseMapMatrix extends Matrix {
type Point = (Int, Int)
private var data = Set[(Int, Int)]()
def apply(p: Point): Boolean = data.contains(p)
def update(p: Point, b: Boolean) = if (b) data += p else data -= p
def newInstance = new SparseMapMatrix()
def iterator = data.iterator
override def toString() = {
val sb = new StringBuilder()
def best(func: (Int, Int) => Int)(p1: Point, p2: Point) = (func(p1._1, p2._1), func(p1._2, p2._2))
val minBoundary = ((0, 0) /: iterator)(best(math.min))
val maxBoundary = ((0, 0) /: iterator)(best(math.max))
for (i <- minBoundary._1 to maxBoundary._1) {
sb.append("\n")
for (j <- minBoundary._2 to maxBoundary._2) {
sb.append(if (this((i, j))) "x" else " ")
}
}
sb.append("\nmin=%s, max=%s" format(minBoundary, maxBoundary))
sb.toString()
}
}
object Engine {
def apply(input: Matrix): Matrix = {
val result = input.newInstance
def block2D(pp: (Int, Int)): Seq[(Int, Int)] =
for (ii <- block1D(pp._1); jj <- block1D(pp._2)) yield (ii, jj)
val liveCells = for (p <- input.iterator.flatMap(block2D).toSet[(Int, Int)].par) yield {
val offsets = block2D(p).filter(_ != p)
val nn = offsets.map(p => input(p)).count(_ == true)
case class State(l: Boolean, n: Int)
val newValue = State(input(p), nn) match {
case State(true, n) if n < 2 => false
case State(true, 2) | State(true, 3) => true
case State(true, n) if n > 3 => false
case State(false, 3) => true
case State(value, _) => value
};
if(newValue) Some(p) else None
}
liveCells.seq.foreach { _ match { case Some(p:Point) => result(p) = true; case _ => ;}}
result
}
}
def block1D(i: Int) = i - 1 to i + 1
}