防御率計算DSLの強化

http://d.hatena.ne.jp/uokumura/20090427/1240854856で作った防御率計算スクリプトですが、成績の投入が

[
  "4/3": [[投手: "安藤", 捕手: "狩野", 投球回: 7, 自責点: 2], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "藤川", 捕手: "狩野", 投球回: 1, 自責点: 0]],
  "4/4": [[投手: "能見", 捕手: "狩野", 投球回: 5, 自責点: 3], [投手: "渡辺", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "阿部", 捕手: "岡崎", 投球回: 1, 自責点: 0], [投手: "藤田", 捕手: "岡崎", 投球回: 1, 自責点: 0], [投手: "江草", 捕手: "清水", 投球回: 1, 自責点: 0]],
  "4/5": [[投手: "福原", 捕手: "狩野", 投球回: 5, 自責点: 6], [投手: "渡辺", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "アッチソン", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "ウィリアムス", 捕手: "岡崎", 投球回: 1, 自責点: 0]],
  "4/7": [[投手: "久保", 捕手: "岡崎", 投球回: 6.1, 自責点: 5], [投手: "ウィリアムス", 捕手: "岡崎", 投球回: 0.1, 自責点: 4], [投手: "藤田", 捕手: "岡崎", 投球回: 0.1, 自責点: 1], [投手: "阿部", 捕手: "岡崎", 投球回: 0.2, 自責点: 0], [投手: "江草", 捕手: "岡崎", 投球回: 1.1, 自責点: 0]],
  "4/8": [[投手: "下柳", 捕手: "狩野", 投球回: 6, 自責点: 2], [投手: "渡辺", 捕手: "狩野", 投球回: 2, 自責点: 0], [投手: "阿部", 捕手: "狩野", 投球回: 1, 自責点: 0]],
  "4/9": [[投手: "石川", 捕手: "狩野", 投球回: 7, 自責点: 4], [投手: "江草", 捕手: "岡崎", 投球回: 1, 自責点: 0], [投手: "アッチソン", 捕手: "岡崎", 投球回: 1, 自責点: 0]],
 "4/10": [[投手: "安藤", 捕手: "狩野", 投球回: 5, 自責点: 3], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 1], [投手: "渡辺", 捕手: "狩野", 投球回: 1, 自責点: 1], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/11": [[投手: "能見", 捕手: "狩野", 投球回: 6.1, 自責点: 4], [投手: "アッチソン", 捕手: "狩野", 投球回: 0.2, 自責点: 0], [投手: "阿部", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/12": [[投手: "福原", 捕手: "狩野", 投球回: 5, 自責点: 3], [投手: "アッチソン", 捕手: "狩野", 投球回: 1, 自責点: 3], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "藤川", 捕手: "狩野", 投球回: 2, 自責点: 0], [投手: "渡辺", 捕手: "狩野", 投球回: 2, 自責点: 0]],
 "4/15": [[投手: "下柳", 捕手: "狩野", 投球回: 6.1, 自責点: 5], [投手: "渡辺", 捕手: "狩野", 投球回: 0.2, 自責点: 0], [投手: "石川", 捕手: "狩野", 投球回: 2, 自責点: 2]],
 "4/16": [[投手: "久保", 捕手: "岡崎", 投球回: 6, 自責点: 2], [投手: "アッチソン", 捕手: "岡崎", 投球回: 2, 自責点: 0], [投手: "藤川", 捕手: "岡崎", 投球回: 1, 自責点: 1]],
 "4/17": [[投手: "安藤", 捕手: "狩野", 投球回: 7, 自責点: 1], [投手: "アッチソン", 捕手: "狩野", 投球回: 0.2, 自責点: 0], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 0.1, 自責点: 0], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/18": [[投手: "能見", 捕手: "狩野", 投球回: 6, 自責点: 0], [投手: "渡辺", 捕手: "狩野", 投球回: 0.1, 自責点: 2], [投手: "アッチソン", 捕手: "狩野", 投球回: 0.2, 自責点: 2], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/19": [[投手: "福原", 捕手: "狩野", 投球回: 5.1, 自責点: 4], [投手: "渡辺", 捕手: "狩野", 投球回: 0.2, 自責点: 0], [投手: "阿部", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/21": [[投手: "下柳", 捕手: "狩野", 投球回: 6, 自責点: 2], [投手: "渡辺", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "江草", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/22": [[投手: "久保", 捕手: "岡崎", 投球回: 5, 自責点: 3], [投手: "阿部", 捕手: "岡崎", 投球回: 1, 自責点: 2], [投手: "阿部", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "筒井", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/23": [[投手: "安藤", 捕手: "狩野", 投球回: 7, 自責点: 0], [投手: "ウィリアムス", 捕手: "狩野", 投球回: 1, 自責点: 0], [投手: "アッチソン", 捕手: "狩野", 投球回: 2, 自責点: 0], [投手: "藤川", 捕手: "狩野", 投球回: 2, 自責点: 0]],
 "4/24": [[投手: "能見", 捕手: "狩野", 投球回: 9, 自責点: 0]],
 "4/25": [[投手: "福原", 捕手: "狩野", 投球回: 8, 自責点: 1], [投手: "筒井", 捕手: "狩野", 投球回: 1, 自責点: 0]],
 "4/26": [[投手: "ジェン", 捕手: "狩野", 投球回: 6, 自責点: 0], [投手: "アッチソン", 捕手: "狩野", 投球回: 0.1, 自責点: 2], [投手: "江草", 捕手: "狩野", 投球回: 1.2, 自責点: 0]],
]

DSLと呼ぶにはあまりに不細工な物でした。
こいつをちょっと強化して、

{[
     "4/3": [安藤x狩野(投球回:7, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:0)],
     "4/4": [能見x狩野(投球回:5, 自責点:3), 渡辺x狩野(投球回:1, 自責点:0), 阿部x岡崎(投球回:1, 自責点:0), 藤田x岡崎(投球回:1, 自責点:0), 江草x清水(投球回:1, 自責点:0)],
     "4/5": [福原x狩野(投球回:5, 自責点:6), 渡辺x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0), ウィリアムスx岡崎(投球回:1, 自責点:0)],
     "4/7": [久保x岡崎(投球回:6.1, 自責点:5), ウィリアムスx岡崎(投球回:0.1, 自責点:4), 藤田x岡崎(投球回:0.1, 自責点:1), 阿部x岡崎(投球回:0.2, 自責点:0), 江草x岡崎(投球回:1.1, 自責点:0)],
     "4/8": [下柳x狩野(投球回:6, 自責点:2), 渡辺x狩野(投球回:2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0)],
     "4/9": [石川x狩野(投球回:7, 自責点:4), 江草x岡崎(投球回:1, 自責点:0), アッチソンx岡崎(投球回:1, 自責点:0)],
    "4/10": [安藤x狩野(投球回:5, 自責点:3), 江草x狩野(投球回:1, 自責点:1), 渡辺x狩野(投球回:1, 自責点:1), ウィリアムスx狩野(投球回:1, 自責点:0)],
    "4/11": [能見x狩野(投球回:6.1, 自責点:4), アッチソンx狩野(投球回:0.2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0)],
    "4/12": [福原x狩野(投球回:5, 自責点:3), アッチソンx狩野(投球回:1, 自責点:3), 江草x狩野(投球回:1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:2, 自責点:0), 渡辺x狩野(投球回:2, 自責点:0)],
    "4/15": [下柳x狩野(投球回:6.1, 自責点:5), 渡辺x狩野(投球回:0.2, 自責点:0), 石川x狩野(投球回:2, 自責点:2)],
    "4/16": [久保x岡崎(投球回:6, 自責点:2), アッチソンx岡崎(投球回:2, 自責点:0), 藤川x岡崎(投球回:1, 自責点:1)],
    "4/17": [安藤x狩野(投球回:7, 自責点:1), アッチソンx狩野(投球回:0.2, 自責点:0), ウィリアムスx狩野(投球回:0.1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/18": [能見x狩野(投球回:6, 自責点:0), 渡辺x狩野(投球回:0.1, 自責点:2), アッチソンx狩野(投球回:0.2, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/19": [福原x狩野(投球回:5.1, 自責点:4), 渡辺x狩野(投球回:0.2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/21": [下柳x狩野(投球回:6, 自責点:2), 渡辺x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/22": [久保x岡崎(投球回:5, 自責点:3), 阿部x岡崎(投球回:1, 自責点:2), 阿部x狩野(投球回:1, 自責点:0), 筒井x狩野(投球回:1, 自責点:0)],
    "4/23": [安藤x狩野(投球回:7, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:2, 自責点:0), 藤川x狩野(投球回:2, 自責点:0)],
    "4/24": [能見x狩野(投球回:9, 自責点:0)],
    "4/25": [福原x狩野(投球回:8, 自責点:1), 筒井x狩野(投球回:1, 自責点:0)],
    "4/26": [ジェンx狩野(投球回:6, 自責点:0), アッチソンx狩野(投球回:0.1, 自責点:2), 江草x狩野(投球回:1.2, 自責点:0)],
    "4/28": [下柳x狩野(投球回:6, 自責点:4), 渡辺x狩野(投球回:0.2, 自責点:0), 江草x狩野(投球回:0.1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:0)],
    "4/29": [安藤x狩野(投球回:3, 自責点:5), 阿部x狩野(投球回:2, 自責点:0), アッチソンx狩野(投球回:2, 自責点:0), 渡辺x狩野(投球回:1, 自責点:1), 筒井x狩野(投球回:1, 自責点:0)],
    "4/30": [久保x狩野(投球回:6.2, 自責点:2), 江草x狩野(投球回:0.1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0)],
    " 5/2": [能見x狩野(投球回:7, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:1)],
    " 5/3": [ジェンx狩野(投球回:5, 自責点:4), 筒井x狩野(投球回:1, 自責点:0), 渡辺x清水(投球回:1, 自責点:0), 阿部x清水(投球回:1, 自責点:0), 金村大x清水(投球回:1, 自責点:0)],
    " 5/4": [下柳x狩野(投球回:6, 自責点:0), 江草x狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0), 筒井x狩野(投球回:1, 自責点:0)],
    " 5/7": [久保x狩野(投球回:6.1, 自責点:2), 江草x狩野(投球回:0.2, 自責点:0), 渡辺x狩野(投球回:1, 自責点:0)],
    " 5/8": [安藤x狩野(投球回:8, 自責点:2)],
    " 5/9": [能見x狩野(投球回:7, 自責点:3), ジェンx狩野(投球回:0.1, 自責点:1), 筒井x狩野(投球回:0.1, 自責点:0), 阿部x狩野(投球回:0.1, 自責点:0)],
]}

と書けるようにしてみました。

まずはテスト

大きく手を入れるので、テストが無いと壊しちゃう可能性大です。さぼってましたが、そろそろテストを書く頃合いのようです。
…と真面目にテストを書き出すと、それだけでへとへとになっちゃうので、ここは手抜きが肝要。とりあえずテストなしで出来そうな、出力フォーマットをちょっと手直しと今日までのデータの投入をしてしまって、まずは結果を取ります。そうすれば、あとはその結果をそのまま使ってassert文を書くだけ。基本的にリファクタリングなので、これで十分なのです。

assert 阪神成績.チーム防御率グラフ ==
         "http://chart.apis.google.com/chart?cht=bvg&chbh=5,0,10&chm=D,0000ff,1,0,2&chco=00ffff,ffffff&chxt=y&chs=620x200&chxr=0,0,10.00&chd=s:MSk9MYibbqSGYbNiAAGNYkMSYANNb,MPWgcbcccdcaaaZaYWWVVWWVVVUUU"

assert 阪神成績.バッテリー別成績 == 
"""|* |*狩野|*岡崎|*清水|*全捕手|
|*能見|40回1/3 自責点12 防御率2.68|-|-|40回1/3 自責点12 防御率2.68|
|*安藤|37回0/3 自責点13 防御率3.16|-|-|37回0/3 自責点13 防御率3.16|
|*下柳|30回1/3 自責点13 防御率3.86|-|-|30回1/3 自責点13 防御率3.86|
|*久保|13回0/3 自責点4 防御率2.77|17回1/3 自責点10 防御率5.19|-|30回1/3 自責点14 防御率4.15|
|*福原|23回1/3 自責点14 防御率5.40|-|-|23回1/3 自責点14 防御率5.40|
|*江草|11回0/3 自責点1 防御率0.82|2回1/3 自責点0 防御率0.00|1回0/3 自責点0 防御率0.00|14回1/3 自責点1 防御率0.63|
|*渡辺|12回1/3 自責点4 防御率2.92|-|1回0/3 自責点0 防御率0.00|13回1/3 自責点4 防御率2.70|
|*アッチソン|10回1/3 自責点7 防御率6.10|3回0/3 自責点0 防御率0.00|-|13回1/3 自責点7 防御率4.72|
|*ジェン|11回1/3 自責点5 防御率3.97|-|-|11回1/3 自責点5 防御率3.97|
|*阿部|6回1/3 自責点0 防御率0.00|2回2/3 自責点2 防御率6.75|1回0/3 自責点0 防御率0.00|10回0/3 自責点2 防御率1.80|
|*ウィリアムス|8回1/3 自責点0 防御率0.00|1回1/3 自責点4 防御率27.00|-|9回2/3 自責点4 防御率3.72|
|*石川|9回0/3 自責点6 防御率6.00|-|-|9回0/3 自責点6 防御率6.00|
|*藤川|7回0/3 自責点1 防御率1.29|1回0/3 自責点1 防御率9.00|-|8回0/3 自責点2 防御率2.25|
|*筒井|5回1/3 自責点0 防御率0.00|-|-|5回1/3 自責点0 防御率0.00|
|*藤田|-|1回1/3 自責点1 防御率6.75|-|1回1/3 自責点1 防御率6.75|
|*金村大|-|-|1回0/3 自責点0 防御率0.00|1回0/3 自責点0 防御率0.00|
|*全投手|225回0/3 自責点80 防御率3.20|29回0/3 自責点18 防御率5.59|4回0/3 自責点0 防御率0.00|258回0/3 自責点98 防御率3.42|
"""

修正内容

計算内容もデータも変わらないので、PichingStatsとCompositePichingStatsはそのままです。

import java.text.SimpleDateFormat

abstract class PitchingStats {
    abstract int getアウト数();
    abstract int get自責点();
    
    def get防御率() {
        if (アウト数 == 0) return null;
        return (自責点 * 3 * 9 / アウト数).setScale(2, BigDecimal.ROUND_HALF_DOWN)
    }
    
    public String toString(){
        if (アウト数 == 0) return "-";
        return "${(int)(アウト数 / 3)}${アウト数 % 3}/3 自責点${自責点} 防御率${防御率}"
    }
}

class CompositePitchingStats extends PitchingStats {
    @Delegate private final List<PitchingStats> リスト
    int getアウト数() {リスト*.アウト数.sum(0)}
    int get自責点() {リスト*.自責点.sum(0)}

    public CompositePitchingStats() {
        this([])
    }
    
    public CompositePitchingStats(List<PitchingStats> list) {
        this.リスト = list
    }
}

「チーム」クラスは「通算成績」クラスに名前を変えました。
こいつが解析する親玉なので、大幅に手を入れてます。といっても構造は変わらないので、解析周りだけですが。

public class 通算成績 {
    private static final FORMAT = new SimpleDateFormat("y/M/d")

    def 全試合 = new CompositePitchingStats()
    
    private def 投手リスト = []
    private def 捕手リスト = []

    public 通算成績(Closure 全試合DSL) {
        全試合.addAll(this.with(全試合DSL).collect{key, value->
            new 試合(通算成績:this, 日付: FORMAT.parse("2009/$key"), バッテリー登板リスト:value)
        })
    }
    
    private get投手(String 投手名) {
        def 投手 = 投手リスト.find{it.名前 == 投手名}
        if (投手 == null) {
            投手リスト << (投手 = new 選手(名前:投手名))
        }
        return 投手
    }
    
    private get捕手(String 捕手名) {
        def 捕手 = 捕手リスト.find{it.名前 == 捕手名}
        if (捕手 == null) {
            捕手リスト << (捕手 = new 選手(名前:捕手名))
        }
        return 捕手
    }
    
    def methodMissing(String name, args) {
        def match = (name =~ /^(.*)x(.*)$/)
        if (!match) {
            throw new MissingMethodException(name, this, args)
        }
        return new バッテリー登板([投手:get投手(match[0][1]), 捕手:get捕手(match[0][2])] + args[0])
    }
    
    String getバッテリー別成績() {
        "|* |*${捕手リスト*.名前.join('|*')}|*全捕手|\n" + 
        投手リスト.sort{[it.アウト数, it.登板回数]}.reverse().collect{投手->
            "|*${投手.名前}|" + 捕手リスト.collect{捕手->
                new CompositePitchingStats(投手.findAll{it.捕手 == 捕手}).toString()
            }.join("|") + "|${投手.toString()}|"
        }.join("\n") + "\n" +
        "|*全投手|${捕手リスト*.toString().join('|')}|${全試合.toString()}|\n"
    }
    
    String getチーム防御率グラフ() {
        def simpleEncode = {values, max->
            def format = ('A'..'Z') + ('a'..'z') + (0..9)
            values.collect {
               (it == null || Double.isNaN(it) || it < 0) ? "_" : format[(int)(it * (format.size()-1) / max)];
            }.join()
        }

        def graphData = [全試合*.防御率, 全試合*.累積成績*.防御率]
        def max = graphData.flatten().max()
        
        "http://chart.apis.google.com/chart" +
            "?cht=bvg&chbh=5,0,10&chm=D,0000ff,1,0,2&chco=00ffff,ffffff&chxt=y" +
            "&chs=${全試合.size()*20+40}x200&chxr=0,0,${max}" + 
            "&chd=s:${graphData.collect{simpleEncode(it, max)}.join(',')}"
    }
}

toStringは「getバッテリー成績」メソッドに名前を変え、またルーズステートメントで書いていたグラフ表示は「getチーム防御率グラフ」メソッドとして取り込んでます。これは内容はそのままの、ただのメソッドの移動です。
ポイントはコンストラクタとmethodMissing。「投手x捕手(投球回:7.1, 自責点: 2)」は、実はこの「通算成績」のメソッド呼び出しになってるんです。コンストラクタで受け取ったDSLをthis.with(DSL)とこのクラスのメソッドを使って実行してやるんですね。で、メソッドの書式で書かれたものをmethodMissingで捕まえる。methodMissingではメソッド名(投手x捕手)から投手名と捕手名を取り、それと()内のその他のデータを加えて1回のバッテリー登板のコンストラクタに入れてます。
これでDSLを実行するだけで、日付をキーにして「その日の試合で登板した各バッテリーごとの成績のリスト」が入ったMapが出来るわけです。ここまで出来れば、後はもう前回作ったスクリプトの世界です。

class 試合 {
    @Delegate CompositePitchingStats バッテリー登板リスト = new CompositePitchingStats()
    
    Date 日付
    def 通算成績
    
    void setバッテリー登板リスト(List<バッテリー登板> data) {
         バッテリー登板リスト = new CompositePitchingStats(data)
    }
    
    public PitchingStats get累積成績() {
        new CompositePitchingStats(通算成績.全試合.findAll{it.日付 <= 日付})
    }
}

class バッテリー登板 extends PitchingStats {
    def 投手
    def 捕手
    int アウト数
    int 自責点
    
    public void set投手(選手 投手) {
        this.@投手 = 投手
        投手.add(this)
    }
    
    public void set捕手(選手 捕手) {
        this.@捕手 = 捕手
        捕手.add(this)
    }
    
    /** 6回と1/3の投球(19アウト)を6.1のような表記で設定するためのセッター */
    public void set投球回(BigDecimal 投球回) {
        アウト数 = 投球回.intValue() * 3 + (投球回*10 - 投球回.intValue() * 10)
    }
    
    def call(Map args) {
        投球回 = args.投球回
        自責点 = args.自責点
        
        return this
    }
    
    String toString() { "${投手.名前} - ${捕手.名前} ${(int)アウト数 / 3}${アウト数 % 3}/3 自責点${自責点}"}
}

class 選手 {
    @Delegate final CompositePitchingStats 登板リスト = new CompositePitchingStats()
    String 名前
    int get登板回数() {return 登板リスト.size()}
    public String toString() {登板リスト.toString()}
}

バッテリー登板のプロパティのセットはDSLがやってくれるようになったので、もう「バッテリー登板」がStringのマップから自分を構築する必要が無くなり、その結果「通算成績(旧「チーム」)」を知ってる必要も無くなりました。その結果ちょっとコードが減ってますが、ほとんどそのままです。

動かしてみる

「通算成績」クラスのコンストラクタに、DSLを記述したクロージャを渡します。
外だししたファイルの内容をクロージャとして取ってくるようにすれば、完全にDSLになるんですけど、まぁそこまではやりませんでした。

def 阪神成績 = new 通算成績({[
     "4/3": [安藤x狩野(投球回:7, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:0)],
     "4/4": [能見x狩野(投球回:5, 自責点:3), 渡辺x狩野(投球回:1, 自責点:0), 阿部x岡崎(投球回:1, 自責点:0), 藤田x岡崎(投球回:1, 自責点:0), 江草x清水(投球回:1, 自責点:0)],
     "4/5": [福原x狩野(投球回:5, 自責点:6), 渡辺x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0), ウィリアムスx岡崎(投球回:1, 自責点:0)],
     "4/7": [久保x岡崎(投球回:6.1, 自責点:5), ウィリアムスx岡崎(投球回:0.1, 自責点:4), 藤田x岡崎(投球回:0.1, 自責点:1), 阿部x岡崎(投球回:0.2, 自責点:0), 江草x岡崎(投球回:1.1, 自責点:0)],
     "4/8": [下柳x狩野(投球回:6, 自責点:2), 渡辺x狩野(投球回:2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0)],
     "4/9": [石川x狩野(投球回:7, 自責点:4), 江草x岡崎(投球回:1, 自責点:0), アッチソンx岡崎(投球回:1, 自責点:0)],
    "4/10": [安藤x狩野(投球回:5, 自責点:3), 江草x狩野(投球回:1, 自責点:1), 渡辺x狩野(投球回:1, 自責点:1), ウィリアムスx狩野(投球回:1, 自責点:0)],
    "4/11": [能見x狩野(投球回:6.1, 自責点:4), アッチソンx狩野(投球回:0.2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0)],
    "4/12": [福原x狩野(投球回:5, 自責点:3), アッチソンx狩野(投球回:1, 自責点:3), 江草x狩野(投球回:1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:2, 自責点:0), 渡辺x狩野(投球回:2, 自責点:0)],
    "4/15": [下柳x狩野(投球回:6.1, 自責点:5), 渡辺x狩野(投球回:0.2, 自責点:0), 石川x狩野(投球回:2, 自責点:2)],
    "4/16": [久保x岡崎(投球回:6, 自責点:2), アッチソンx岡崎(投球回:2, 自責点:0), 藤川x岡崎(投球回:1, 自責点:1)],
    "4/17": [安藤x狩野(投球回:7, 自責点:1), アッチソンx狩野(投球回:0.2, 自責点:0), ウィリアムスx狩野(投球回:0.1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/18": [能見x狩野(投球回:6, 自責点:0), 渡辺x狩野(投球回:0.1, 自責点:2), アッチソンx狩野(投球回:0.2, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/19": [福原x狩野(投球回:5.1, 自責点:4), 渡辺x狩野(投球回:0.2, 自責点:0), 阿部x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/21": [下柳x狩野(投球回:6, 自責点:2), 渡辺x狩野(投球回:1, 自責点:0), 江草x狩野(投球回:1, 自責点:0)],
    "4/22": [久保x岡崎(投球回:5, 自責点:3), 阿部x岡崎(投球回:1, 自責点:2), 阿部x狩野(投球回:1, 自責点:0), 筒井x狩野(投球回:1, 自責点:0)],
    "4/23": [安藤x狩野(投球回:7, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:2, 自責点:0), 藤川x狩野(投球回:2, 自責点:0)],
    "4/24": [能見x狩野(投球回:9, 自責点:0)],
    "4/25": [福原x狩野(投球回:8, 自責点:1), 筒井x狩野(投球回:1, 自責点:0)],
    "4/26": [ジェンx狩野(投球回:6, 自責点:0), アッチソンx狩野(投球回:0.1, 自責点:2), 江草x狩野(投球回:1.2, 自責点:0)],
    "4/28": [下柳x狩野(投球回:6, 自責点:4), 渡辺x狩野(投球回:0.2, 自責点:0), 江草x狩野(投球回:0.1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:0)],
    "4/29": [安藤x狩野(投球回:3, 自責点:5), 阿部x狩野(投球回:2, 自責点:0), アッチソンx狩野(投球回:2, 自責点:0), 渡辺x狩野(投球回:1, 自責点:1), 筒井x狩野(投球回:1, 自責点:0)],
    "4/30": [久保x狩野(投球回:6.2, 自責点:2), 江草x狩野(投球回:0.1, 自責点:0), ウィリアムスx狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0)],
    " 5/2": [能見x狩野(投球回:7, 自責点:2), ウィリアムスx狩野(投球回:1, 自責点:0), 藤川x狩野(投球回:1, 自責点:1)],
    " 5/3": [ジェンx狩野(投球回:5, 自責点:4), 筒井x狩野(投球回:1, 自責点:0), 渡辺x清水(投球回:1, 自責点:0), 阿部x清水(投球回:1, 自責点:0), 金村大x清水(投球回:1, 自責点:0)],
    " 5/4": [下柳x狩野(投球回:6, 自責点:0), 江草x狩野(投球回:1, 自責点:0), アッチソンx狩野(投球回:1, 自責点:0), 筒井x狩野(投球回:1, 自責点:0)],
    " 5/7": [久保x狩野(投球回:6.1, 自責点:2), 江草x狩野(投球回:0.2, 自責点:0), 渡辺x狩野(投球回:1, 自責点:0)],
    " 5/8": [安藤x狩野(投球回:8, 自責点:2)],
    " 5/9": [能見x狩野(投球回:7, 自責点:3), ジェンx狩野(投球回:0.1, 自責点:1), 筒井x狩野(投球回:0.1, 自責点:0), 阿部x狩野(投球回:0.1, 自責点:0)],
]})

println 阪神成績.バッテリー別成績
println 阪神成績.チーム防御率グラフ

以下のような結果が取れました。

狩野 岡崎 清水 全捕手
能見 40回1/3 自責点12 防御率2.68 - - 40回1/3 自責点12 防御率2.68
安藤 37回0/3 自責点13 防御率3.16 - - 37回0/3 自責点13 防御率3.16
下柳 30回1/3 自責点13 防御率3.86 - - 30回1/3 自責点13 防御率3.86
久保 13回0/3 自責点4 防御率2.77 17回1/3 自責点10 防御率5.19 - 30回1/3 自責点14 防御率4.15
福原 23回1/3 自責点14 防御率5.40 - - 23回1/3 自責点14 防御率5.40
江草 11回0/3 自責点1 防御率0.82 2回1/3 自責点0 防御率0.00 1回0/3 自責点0 防御率0.00 14回1/3 自責点1 防御率0.63
渡辺 12回1/3 自責点4 防御率2.92 - 1回0/3 自責点0 防御率0.00 13回1/3 自責点4 防御率2.70
アッチソン 10回1/3 自責点7 防御率6.10 3回0/3 自責点0 防御率0.00 - 13回1/3 自責点7 防御率4.72
ジェン 11回1/3 自責点5 防御率3.97 - - 11回1/3 自責点5 防御率3.97
阿部 6回1/3 自責点0 防御率0.00 2回2/3 自責点2 防御率6.75 1回0/3 自責点0 防御率0.00 10回0/3 自責点2 防御率1.80
ウィリアムス 8回1/3 自責点0 防御率0.00 1回1/3 自責点4 防御率27.00 - 9回2/3 自責点4 防御率3.72
石川 9回0/3 自責点6 防御率6.00 - - 9回0/3 自責点6 防御率6.00
藤川 7回0/3 自責点1 防御率1.29 1回0/3 自責点1 防御率9.00 - 8回0/3 自責点2 防御率2.25
筒井 5回1/3 自責点0 防御率0.00 - - 5回1/3 自責点0 防御率0.00
藤田 - 1回1/3 自責点1 防御率6.75 - 1回1/3 自責点1 防御率6.75
金村大 - - 1回0/3 自責点0 防御率0.00 1回0/3 自責点0 防御率0.00
全投手 225回0/3 自責点80 防御率3.20 29回0/3 自責点18 防御率5.59 4回0/3 自責点0 防御率0.00 258回0/3 自責点98 防御率3.42

チーム防御率はそろそろ頭打ちでしょうか。2点台を伺うようになると、貯金も出来てくるんでしょうけどねー。まぁ、それよりも打線が…