キミのコードが汚い理由 − @IT をうけた、Haskell 脳その他の方々による Ruby 版などを拝見。
- sshi.Continual - 美しいコード?
- 趣味的にっき - テニスの勝負判定コード
- Tommy Heartbeat 2nd - 美しいプログラムコードとは。
- バカが征く - めんどいからRubyで書くけど…
それぞれに興味深かったので、私なりのメッセージング脳だとどのようになるのか、Smalltalk 版と、それをほぼ直訳した Ruby 版を書いてみました。
まずベースとなる Smalltalk 版をメタ情報を除いた加工コードで(ファイルイン不可)。
Object subclass: #TennisSetScorer instanceVariableNames: 'games' TennisSetScorer >> initialize games := Dictionary new. games at: #player1 put: 0. games at: #player2 put: 0 TennisSetScorer >> gameWonBy: player games at: player put: (games at: player) + 1 TennisSetScorer >> leader ^ games keyAtValue: self leaderGames TennisSetScorer >> leaderGames ^ games values max TennisSetScorer >> opponentGames ^ games values min TennisSetScorer >> isComplete ^ self leaderGames = 7 or: [(self leaderGames = 6) & (self opponentGames < 5)] TennisSetScorer >> isTied ^ self leaderGames = self opponentGames TennisSetScorer >> stateString | info | info := {self leader capitalized. self leaderGames. self opponentGames}. self isTied ifTrue: [^ 'Set is tied at ', games values anyOne asString]. self isComplete ifTrue: [^ '{1} wins the set {2} - {3}' format: info]. ^ '{1} leads {2} - {3}' format: info
| set | set := TennisSetScorer new. World findATranscript: nil. [set isComplete] whileFalse: [ set gameWonBy: #(player1 player2) atRandom. Transcript cr; show: set stateString]
出力例。
Player2 leads 1 - 0 Set is tied at 1 Player2 leads 2 - 1 Set is tied at 2 Player1 leads 3 - 2 Set is tied at 3 Player2 leads 4 - 3 Player2 leads 5 - 3 Player2 leads 5 - 4 Set is tied at 5 Player1 leads 6 - 5 Set is tied at 6 Player1 wins the set 7 - 6
これを、Ruby に直訳すると…
class TennisSetScorer def initialize; @games = {:player1 => 0, :player2 => 0} end def game_won_by(player); @games[player] += 1 end def leader; @games.index(leader_games) end def leader_games; @games.values.max end def opponent_games; @games.values.min end def is_complete; leader_games == 7 or (leader_games == 6 && opponent_games < 5) end def is_tied; leader_games == opponent_games end def state_str leader_str = leader.to_s.capitalize if self.is_tied "Set is tied at #{leader_games}" elsif self.is_complete "#{leader_str} wins the set #{leader_games} - #{opponent_games}" else "#{leader_str} leads #{leader_games} - #{opponent_games}" end end end class Array; def at_rand; self[rand(size)] end end set = TennisSetScorer.new until set.is_complete do set.game_won_by([:player1, :player2].at_rand) puts set.state_str end
しいて方針めいたものを書くとすれば、代入よりはこまめにメソッド化して #state_str の中味を簡素化、かつ、#is_tied や #is_complete を介してより叙述的にする…といった感じでしょうか(そのため、条件分岐の際に self は省略しないほうがいいかなぁと思って追加したらドットを忘れていました。スミマセン(^_^;) あーっと。Ruby の作法だと #tied? とか #complete? というふうに名付けるべきなので self は省略してもそれっぽく読めますね…)。
…と書いていて気が付いたのですが、この方針にしたがうならば、Smalltalk のシンボルと違って、いちいち #to_s を噛まさないと #capitalize が使えない Ruby 1.8 のシンボルの場合、これもメソッド化して“隠して”しまったほうがいっそすっきりしますね(最初から頭を大文字にしたシンボル and/or 文字列を使え!…ちゅうか、そもそもハードコードするな!とかいう声も聞こえてきそうですが(^_^;))。
ということで、tied? とかもついでに直しつつ…
class TennisSetScorer def initialize; @games = {:player1 => 0, :player2 => 0} end def game_won_by(player); @games[player] += 1 end def leader; @games.index(leader_games) end def leader_name; leader.to_s.capitalize end def leader_games; @games.values.max end def opponent_games; @games.values.min end def complete?; leader_games == 7 or (leader_games == 6 && opponent_games < 5) end def tied?; leader_games == opponent_games end def state_str return "Set is tied at #{leader_games}" if tied? return "#{leader_name} wins the set #{leader_games} - #{opponent_games}" if complete? "#{leader_name} leads #{leader_games} - #{opponent_games}" end end class Array; def at_rand; self[rand(size)] end end set = TennisSetScorer.new until set.complete? do set.game_won_by([:player1, :player2].at_rand) puts set.state_str end
これで #state_str を、条件分岐記述のみに簡素化できました。スンビャラスィ。