2010-06-17 95 views
5

我是一個Scala n00b(但我有與其他語言的經驗),我正在學習語言,因爲我發現時間 - 非常享受它迄今!評論我的Scala代碼

通常當學習一種新的語言時,我所做的第一件事是實現Conway's Game of Life,因爲它足夠複雜,足以讓人對語言有一個很好的理解,但是它的範圍足夠小以便能夠在幾個小時內wh起其中大部分花費在語法上)。

Anyhoo,經歷了Scala的這個練習,我希望Scala大師可能會看看我已經結束的代碼並提供反饋。我追求任何東西 - 算法改進(特別是併發解決方案!),文體改進,替代API或語言構造,對我的函數名稱的長度感到厭惡 - 無論您有什麼反饋,我都渴望聽到它!

您應該可以通過scala GameOfLife.scala運行以下腳本 - 默認情況下它將運行帶有單個滑翔機的20x20板 - 請隨時嘗試。

// CONWAY'S GAME OF LIFE (SCALA) 
abstract class GameOfLifeBoard(val aliveCells : Set[Tuple2[Int, Int]]) 
{ 
    // Executes a "time tick" - returns a new board containing the next generation 
    def tick : GameOfLifeBoard 

    // Is the board empty? 
    def empty : Boolean = aliveCells.size == 0 

    // Is the given cell alive? 
    protected def alive(cell : Tuple2[Int, Int]) : Boolean = aliveCells contains cell 

    // Is the given cell dead? 
    protected def dead(cell : Tuple2[Int, Int]) : Boolean = !alive(cell) 

} 


class InfiniteGameOfLifeBoard(aliveCells : Set[Tuple2[Int, Int]]) 
    extends GameOfLifeBoard(aliveCells) 
{ 
    // Executes a "time tick" - returns a new board containing the next generation 
    override def tick : GameOfLifeBoard = new InfiniteGameOfLifeBoard(nextGeneration) 

    // The next generation of this board 
    protected def nextGeneration : Set[Tuple2[Int, Int]] = aliveCells flatMap neighbours filter shouldCellLiveInNextGeneration 

    // Should the given cell should live in the next generation? 
    protected def shouldCellLiveInNextGeneration(cell : Tuple2[Int, Int]) : Boolean = (alive(cell) && (numberOfAliveNeighbours(cell) == 2 || numberOfAliveNeighbours(cell) == 3)) || 
                        (dead(cell) && numberOfAliveNeighbours(cell) == 3) 

    // The number of alive neighbours for the given cell 
    protected def numberOfAliveNeighbours(cell : Tuple2[Int, Int]) : Int = aliveNeighbours(cell) size 

    // Returns the alive neighbours for the given cell 
    protected def aliveNeighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = aliveCells intersect neighbours(cell) 

    // Returns the coordinates of all of the neighbouring cells of the given cell 
    protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = Set((cell._1-1, cell._2-1), (cell._1, cell._2-1), (cell._1+1, cell._2-1), 
                        (cell._1-1, cell._2),       (cell._1+1, cell._2), 
                        (cell._1-1, cell._2+1), (cell._1, cell._2+1), (cell._1+1, cell._2+1)) 

    // Information on where the currently live cells are 
    protected def xVals = aliveCells map { cell => cell._1 } 
    protected def xMin = (xVals reduceLeft (_ min _)) - 1 
    protected def xMax = (xVals reduceLeft (_ max _)) + 1 
    protected def xRange = xMin until xMax + 1 

    protected def yVals = aliveCells map { cell => cell._2 } 
    protected def yMin = (yVals reduceLeft (_ min _)) - 1 
    protected def yMax = (yVals reduceLeft (_ max _)) + 1 
    protected def yRange = yMin until yMax + 1 


    // Returns a simple graphical representation of this board 
    override def toString : String = 
    { 
    var result = "" 

    for (y <- yRange) 
    { 
     for (x <- xRange) 
     { 
     if (alive (x,y)) result += "# " 
     else result += ". " 
     } 

     result += "\n" 
    } 

    result 
    } 

    // Equality stuff 
    override def equals(other : Any) : Boolean = 
    { 
    other match 
    { 
     case that : InfiniteGameOfLifeBoard => (that canEqual this) && 
              that.aliveCells == this.aliveCells 
     case _ => false 
    } 
    } 

    def canEqual(other : Any) : Boolean = other.isInstanceOf[InfiniteGameOfLifeBoard] 

    override def hashCode = aliveCells.hashCode 

} 


class FiniteGameOfLifeBoard(val boardWidth : Int, val boardHeight : Int, aliveCells : Set[Tuple2[Int, Int]]) 
    extends InfiniteGameOfLifeBoard(aliveCells) 
{ 
    override def tick : GameOfLifeBoard = new FiniteGameOfLifeBoard(boardWidth, boardHeight, nextGeneration) 

    // Returns the coordinates of all of the neighbouring cells of the given cell 
    override protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = super.neighbours(cell) filter { cell => cell._1 >= 0 && cell._1 < boardWidth && 
                                   cell._2 >= 0 && cell._2 < boardHeight } 

    // Information on where the currently live cells are 
    override protected def xRange = 0 until boardWidth 
    override protected def yRange = 0 until boardHeight 

    // Equality stuff 
    override def equals(other : Any) : Boolean = 
    { 
    other match 
    { 
     case that : FiniteGameOfLifeBoard => (that canEqual this) && 
              that.boardWidth == this.boardWidth && 
              that.boardHeight == this.boardHeight && 
              that.aliveCells == this.aliveCells 
     case _ => false 
    } 
    } 

    override def canEqual(other : Any) : Boolean = other.isInstanceOf[FiniteGameOfLifeBoard] 

    override def hashCode : Int = 
    { 
    41 * (
     41 * (
     41 + super.hashCode 
    ) + boardHeight.hashCode 
    ) + boardWidth.hashCode 
    } 

} 


class GameOfLife(initialBoard: GameOfLifeBoard) 
{ 
    // Run the game of life until the board is empty or the exact same board is seen twice 
    // Important note: this method does NOT necessarily terminate!! 
    def go : Unit = 
    { 
    var currentBoard = initialBoard 
    var previousBoards = List[GameOfLifeBoard]() 

    while (!currentBoard.empty && !(previousBoards contains currentBoard)) 
    { 
     print(27.toChar + "[2J") // ANSI: clear screen 
     print(27.toChar + "[;H") // ANSI: move cursor to top left corner of screen 
     println(currentBoard.toString) 
     Thread.sleep(75) 

     // Warning: unbounded list concatenation can result in OutOfMemoryExceptions ####TODO: replace with LRU bounded list 
     previousBoards = List(currentBoard) ::: previousBoards 
     currentBoard = currentBoard tick 
    } 

    // Print the final board 
    print(27.toChar + "[2J") // ANSI: clear screen 
    print(27.toChar + "[;H") // ANSI: move cursor to top left corner of screen 
    println(currentBoard.toString) 
    } 
} 



// Script starts here 
val simple = Set((1,1)) 
val square = Set((4,4), (4,5), (5,4), (5,5)) 
val glider = Set((2,1), (3,2), (1,3), (2,3), (3,3)) 

val initialBoard = glider 

(new GameOfLife(new FiniteGameOfLifeBoard(20, 20, initialBoard))).go 
//(new GameOfLife(new InfiniteGameOfLifeBoard(initialBoard))).go 

// COPYRIGHT PETER MONKS 2010 

一個具體問題:我刪除了所有的函數的返回類型在一個點(Scala的類型推斷FTW!),卻發現代碼實際上得難以閱讀。有沒有關於何時離開返回類型的任何約定,讓Scala找出它們(除了那些必要的情況之外)?

+1

放在同一行左括號。如果你用Java編碼,那麼在那裏也是一樣。 – OscarRyz 2010-06-17 23:10:37

+1

謝謝,但我是一個老傻瓜ANSI-C風格的傢伙,不太可能很快改變我的括號/縮進樣式。 ;-) – Peter 2010-06-17 23:16:51

+1

這就是我想的:)但是,你說:*批評我的代碼* :)我認爲這就像學習一門自然語言,你將永遠有一個外國口音。 – OscarRyz 2010-06-17 23:43:36

回答

1

Tuple2(和所有TupleN)類型值可以用括號寫:

type cell = (Int, Int) 
// same as: 
type cell = Tuple2[Int, Int] 

val cell00 = (0, 0) 
// same as: 
val cell00 = Tuple2(0, 0) 
+0

謝謝!這是否意味着在「type cell =(Int,Int)」的情況下,「cell」只是「Tuple2 [Int,Int]」的語法糖,還是它們實際上是不同的類型? – Peter 2010-06-18 01:22:46

+0

@彼得:他們*不是*不同類型。 Scala的'type'就像Haskell's一樣,它只爲現有類型定義別名,而不是新類型。 – 2010-06-18 05:00:01

+0

不錯!是否有使用initial-capital作爲類型名稱的約定?即。 「單元格」優先於「單元格」 – Peter 2010-06-18 18:19:38

1

for循環創建了toString的結果是少了很多緊湊比可能有關係。

你想

yRange.map(y => { 
    xRange.map(x => if (alive(x,y)) "#" else ".").mkString(" ") 
}).mkString("\n") 

而且a until b+1更好寫成a to b

P.S. (i => {這個事情是我從單獨的大括號樣式切換到行尾樣式的主要原因。

+0

謝謝!我一直在想,是否有更多功能的toString版本,但沒有進一步研究。並感謝提到「來」 - 已經錯過了某種方式。 關於花括號的定位,在Groovy(我也很熟悉)中,我發現自己將封閉的花括號放在換行符上 - 在我眼中,更容易快速掃描大文件以查找「代碼塊」 。這就是說它確實會導致一些不尋常的情況(比如「})」,或許Scala語法使得這些奇怪的情況更加普遍。 – Peter 2010-06-18 01:26:34

2

元組很好,但如果您創建並使用「單元」類,事情可能會更容易。

+0

謝謝!鑑於一個單元不過是一對Ints,你會建議把它作爲一個新的類,還是像Randall上面所說的那樣作爲一個「類型」? – Peter 2010-06-18 01:27:55

+3

新類: case class Cell(val x:Int,val y:Int) 這不是一個巨大的勝利,但是Scala中的新類很容易,而.x和.y只是看起來好多了。拋出案例類模式匹配和2.8的「複製」方法,似乎是值得的。 – 2010-06-18 02:58:48

1

我對添加併發性有一個好主意,但從來沒有去過。無論如何,here's它是我自己的一個實現。我計劃在這個主題上發表博客 - 還有很多關於這個主題的變化 - 但生活就這樣陷入了困境。 :-)

至於你的問題,我寧願總是聲明我的方法的返回類型,除非它真的是一個非常簡單的一個班輪。

+0

我想這取決於你的工具首選項。大多數IDE都會顯示返回類型,不需要再看兩次。好的命名也有助於:如果你需要一個類型註釋來找出一個名爲'toString'的方法返回一個'String',你可能不應該編程:-)類似於名爲'canSomething','isSomething','' hasSomething'以及'equals'或'contains'和返回類型'Boolean'。 – 2010-06-18 01:44:19

+1

@Jörg不僅僅是工具首選項!這可能會讓你感到驚訝,但也有很多人認爲方法命名是對壞類型的柺杖,並且函數的類型應該是理解它的主要基礎。這些人通常都是不斷要求獲得相當於http://www.haskell.org/hoogle/的Scala的人。 :-) – 2010-06-18 13:51:31

5

可以更換

protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = 
    Set((cell._1-1, cell._2-1), (cell._1, cell._2-1), (cell._1+1, cell._2-1), 
    (cell._1-1, cell._2),       (cell._1+1, cell._2), 
    (cell._1-1, cell._2+1), (cell._1, cell._2+1), (cell._1+1, cell._2+1)) 

有:

protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = { 
    val direction = Set(-1,0,1) 
    for(x <- direction; y<-direction; if(x !=0 || y != 0)) 
    yield (cell._1+x,cell._2+y) 
} 

var previousBoards, var currentBoard:如果定義Go方法遞歸可以定義爲這些瓦爾斯。

while (!currentBoard.empty && !(previousBoards contains currentBoard)):如果您實施IterableInfiniteGameOfLifeBoard您可以在這裏使用for表達式。

顯而易見:爲什麼scaladoc無法處理格式的文檔?

0

​​3210很短的康威的生活寫在純功能性風格