2010-06-17 8 views
5

मैं एक स्कैला एन 00 बी (लेकिन अन्य भाषाओं के साथ अनुभव कर रहा हूं) और भाषा सीख रहा हूं क्योंकि मुझे समय लगता है - अब तक इसका आनंद ले रहे हैं!क्रिटिक मेरे स्कैला कोड

आमतौर पर जब कोई नई भाषा सीखती है तो पहली बात यह है कि मैं Conway's Game of Life लागू करता हूं, क्योंकि यह भाषा की अच्छी समझ देने के लिए पर्याप्त जटिल है, लेकिन कुछ घंटों में चाबुक करने में सक्षम होने के लिए पर्याप्त छोटा है (जिनमें से अधिकांश वाक्यविन्यास के साथ कुश्ती बिताई जाती है)।

कोई भी, स्कैला I के साथ इस अभ्यास के माध्यम से जाने के बाद, उम्मीद कर रहा था कि स्कैला गुरु वहां मौजूद कोड को देख सकते हैं और उस पर प्रतिक्रिया प्रदान कर सकते हैं। मैं कुछ भी बाद में हूं - एल्गोरिदमिक सुधार (विशेष रूप से समवर्ती समाधान!), स्टाइलिस्टिक सुधार, वैकल्पिक एपीआई या भाषा संरचनाएं, मेरे फ़ंक्शन नामों की लंबाई पर घृणा - जो भी फीडबैक आपको मिला है, मैं इसे सुनना चाहता हूं!

आपको 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 

एक विशिष्ट प्रश्न: मैं एक बिंदु (! स्काला प्रकार निष्कर्ष FTW) पर काम करता है सब की तरफ से वापसी प्रकार हटा दिया, लेकिन पाया कोड वास्तव में पढ़ने के लिए कठिन हो गया। क्या स्काला को उन्हें बाहर निकालने के लिए रिटर्न प्रकार छोड़ने के बारे में कोई सम्मेलन है (उन मामलों से परे जहां वे आवश्यक हैं)?

+1

रखें एक ही पंक्ति में ब्रेस खोलने। यदि आप जावा में कोड करते हैं तो वही करते हैं। – OscarRyz

+1

धन्यवाद, लेकिन मैं एक पुरानी स्कूल एएनएसआई-सी शैली लड़का हूं, इसलिए जल्द ही मेरी ब्रेस/इंडेंटेशन शैली को बदलने की संभावना नहीं है। ;-) – Peter

+1

यही वही है जो मैंने माना :) लेकिन, आपने कहा: * मेरे कोड की आलोचना करें * :) मुझे लगता है कि यह एक प्राकृतिक भाषा सीखने जैसा है, आपके पास हमेशा एक विदेशी उच्चारण होगा। – OscarRyz

उत्तर

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

धन्यवाद! क्या इसका मतलब है कि "टाइप सेल = (इंट, इंट)" मामले में, "सेल" केवल "टुपले 2 [इंट, इंट]" के लिए सिंटैक्टिक चीनी है, या वे वास्तव में विभिन्न प्रकार हैं? – Peter

+0

@ पीटर: वे * अलग * प्रकार नहीं हैं। स्कैला का 'टाइप' हैस्केल की तरह है कि यह मौजूदा प्रकारों के लिए केवल उपनाम परिभाषित करता है, न कि नए। –

+0

अच्छा! क्या नामों के नामों के लिए प्रारंभिक पूंजी का उपयोग करने के लिए कोई सम्मेलन है? अर्थात। "सेल" को "सेल" – Peter

1

toString परिणाम बनाने के लिए पाश के लिए बहुत कम कॉम्पैक्ट की तुलना में यह हो सकता है।

आप

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

चाहते इसके अलावा a until b+1 बेहतर a to b के रूप में लिखा है।

पीएस (i => { चीज मुख्य कारण है कि मैंने अलग-अलग लाइन ब्रेस शैली से अंत-शैली की शैली में स्विच किया।

+0

पर पसंद किया जाता है धन्यवाद! मैं सोच रहा था कि क्या स्ट्रिंग का एक और अधिक कार्यात्मक संस्करण था, लेकिन इसमें आगे नहीं देखा था। और "टू" का उल्लेख करने के लिए भी धन्यवाद - किसी भी तरह से चूक गया था। घुंघराले ब्रेसिज़ की स्थिति के बारे में, ग्रोवी में (जो मैं भी काफी परिचित हूं) मैं खुद को घुमावदार ब्रेसिज़ को एक नई लाइन पर बंद करने के लिए ढूंढता हूं - मेरी आंखों में "कोड ब्लॉक" के लिए एक बड़ी फ़ाइल को जल्दी से स्कैन करना आसान है। । उस ने कहा कि इसका परिणाम एक ही पंक्ति पर कुछ असामान्य मामलों (जैसे "})" होता है) और शायद स्कैला सिंटैक्स उन विषम मामलों को और भी प्रचलित बनाता है। – Peter

2

टुपल्स बहुत अच्छे हैं, लेकिन यदि आपने "सेल" वर्ग बनाया और उपयोग किया है तो चीजें आसान हो सकती हैं।

+0

धन्यवाद! यह देखते हुए कि एक सेल इंट्स की एक जोड़ी से ज्यादा कुछ नहीं है, क्या आप इसे एक नई कक्षा के रूप में करने या "प्रकार" के रूप में सुझाव देंगे क्योंकि रैंडल ऊपर बताता है? – Peter

+3

नई कक्षा: केस क्लास सेल (वैल एक्स: इंट, वैल वाई: इंट) यह एक बड़ी जीत नहीं है, लेकिन स्कैला में एक नई कक्षा इतनी आसान है, और .x और .y बस इतना अच्छा दिखता है। वर्ग पैटर्न मिलान और 2.8 की "प्रतिलिपि" विधि के मामले में फेंको, और ऐसा लगता है कि यह इसके लायक है। –

1

मुझे समेकन जोड़ने के लिए एक अच्छा विचार था, लेकिन इसे कभी नहीं मिला। वैसे भी, here's इसके अपने कार्यान्वयन में से एक। मैंने इसके बारे में ब्लॉग करने की योजना बनाई - और थीम पर कई बदलाव - लेकिन जीवन रास्ते में आया। :-)

आपके प्रश्न के लिए, मैं हमेशा अपने तरीकों के रिटर्न प्रकार की घोषणा करना पसंद करता हूं, जब तक कि यह वास्तव में एक बहुत ही सरल एक लाइनर न हो।

+0

मुझे लगता है कि यह आपकी टूल वरीयताओं पर निर्भर करता है। अधिकांश आईडीई वैसे भी रिटर्न प्रकार दिखाते हैं, उन्हें दो बार देखने की आवश्यकता नहीं होती है। अच्छा नामकरण भी मदद करता है: यदि आपको यह पता लगाने के लिए एक प्रकार की एनोटेशन की आवश्यकता है कि 'toString' नामक एक विधि 'स्ट्रिंग' लौटाती है, तो आपको शायद प्रोग्रामिंग नहीं होनी चाहिए :-)' कुछ कर सकते हैं 'नामक विधियों के लिए,' कुछ है ',' इसमें कुछ 'साथ ही' बराबर 'या' है 'और' बूलियन 'का रिटर्न प्रकार है। –

+1

@ जोर्ग न केवल टूल वरीयताओं पर! यह आपको आश्चर्यचकित कर सकता है, लेकिन बहुत से लोग हैं जो सोचते हैं कि विधि नामकरण खराब प्रकारों के आसपास एक क्रैच है, और यह समझने के लिए कि फ़ंक्शन के प्रकार प्राथमिक आधार होना चाहिए। ये आमतौर पर वही लोग होते हैं जो स्केल-समकक्ष http://www.haskell.org/hoogle/ के बराबर पूछते रहते हैं। :-) –

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: आपको यात्रा के दौरान विधि को परिभाषित तो रिकर्सिवली आप Vals के रूप में निर्धारित कर सकते हैं।

while (!currentBoard.empty && !(previousBoards contains currentBoard)): यदि आप के साथ Iterable लागू करते हैं तो आप यहां अभिव्यक्ति के लिए उपयोग कर सकते हैं।

स्पष्ट: प्रारूप में दस्तावेज़ क्यों है जिसे स्केलडोक द्वारा संसाधित नहीं किया जा सकता है?

0

here's एक बहुत ही कम कोनवे के जीवन एक शुद्ध कार्यात्मक शैली में लिखा

संबंधित मुद्दे