読者です 読者をやめる 読者になる 読者になる

Homeの4問目(Pawn Brotherhood)

CheckiO

py.checkio.org

チェスのポーンに関する問題で、
自軍にn個のポーンがあるとき安全なポーン(他のポーンに守られている)の総数を導き出すというものです。
まずポーンの性質ですが斜め前(左右)に1マスずつしか進めません。つまり安全なポーンとは他のポーンの斜め前(左右)にあるポーンということになります。
またポーンの配置数は最大で8でポーンの位置はローマ字と数字の組み合わせ盤面のマスを表したもので与えられます。

一旦考え方として一個ずつ配置済みポーンの座標から安全な座標を洗い出して、その座標上にあるポーンの数を数えるという方向性でやってみました。

前述したように配置済みのポーンの座標は
{"b4", "d4", "f4", "c3", "e3", "g5", "d2"}
というローマ字と数字の組み合わせで形で与えられます。
ちなみにpythonの中括弧はディクショナリ(連想配列)らしですね。
でも、キーがない場合の連想配列ってなんなんでしょうか?
イテラブルであるということを便宜的に表現してるだけなんですかね?
とりあえず普通の配列として扱ってみるか、と思ってやってみたのですがうまく取り出せない。。
で、色々やった結果list()で通常の配列に変換できたので一旦それでなんとかしいてみました。

重複を外すのを忘れててのエラーが出たりはしたもののとりあえず下記で通りました。

def safe_pawns(pawns):
    safespots =
    cols = { "a":0, "b":1, "c":2, "d":3, "e":4, "f":5, "g":6, "h":7 }
    pawns = list(pawns)
    for i in pawns:
        target =

        for j in pawns:
            if int(i[1])+1 == int(j[1]):
                target.append(j)
         for k in target:
            if abs(cols.get(i[0])-cols.get(k[0])) == 1:
                safespots.append(k)
                if safespots.count(k)>1:
                    safespots.pop()
    return len(safespots)

とはいえforがいっぱいで気持ち悪いので書き換えたいですね。
上記でやっている、一個取り出して安全な行i[1] + 1にあるpawnを引っ張って、
i[0]-j[0]の絶対値が1のものがあったら安全なポーンをカウントしている配列を調べて重複がなければ入れる。
という処理あたりが改善しやすそうということでそのあたりを中心に検討していたのですが、行を評価してということをやるよりダイレクトに座標を出した方が簡単になりそうです。

def safe_pawns(pawns):
    safespots =
        cols = { "a":0, "b":1, "c":2, "d":3, "e":4, "f":5, "g":6, "h":7 }
        pawns = list(pawns)
        for i in pawns:
            saferow = i[1]+1
            safeL = cols.key(cols.get(i[0])-1) + saferow
            safeR = cols.key(cols.get(i[0])+1) + saferow
            if safeL in pawns:
                if safespots.count(safeL) < 1: safespots.append(safeL)
            if safeR in pawns:
                if safespots.count(safeR) < 1: safespots.append(safeR)
    return len(safespots)

strだったりintだったりが入り混じりすぎて細々エラーが出ていたので、
修正していたら配列の長さを超えて取得しようとしてるというエラーが出てたので修正。

def safe_pawns(pawns):
    safespots =
    cols = { "a":0, "b":1, "c":2, "d":3, "e":4, "f":5, "g":6, "h":7, "":"" }
    pawns = list(pawns)
    for i in pawns:
        saferow = int(i[1])+1
        safeL = list(cols.keys())[cols.get(i[0])-1] + str(saferow)
        safeR = list(cols.keys())[cols.get(i[0])+1] + str(saferow)
        if safeL in pawns:
            if safespots.count(safeL) < 1: safespots.append(safeL)
        if safeR in pawns:
            if safespots.count(safeR) < 1: safespots.append(safeR)
    return len(safespots)


もう少しなんとかしたい、
ということで色々調べた結果ord()でASCIIコードを拾えてchr()で戻せるみたいですね。

def safe_pawns(pawns):
    safespots =
    for i in pawns:
        saferow = str(int(i[1])+1)
        safeL = chr(ord(i[0])-1)+saferow
        safeR = chr(ord(i[0])+1)+saferow
        if safeL in pawns:
            if safespots.count(safeL) < 1: safespots.append(safeL)
        if safeR in pawns:
            if safespots.count(safeR) < 1: safespots.append(safeR)
    return len(safespots)

基準とするポーンの座標を守る側ではなく守られる側にすると変なifが外せることに築いたので修正しました。

def safe_pawns(pawns):
    safespots =
    for i in pawns:
        saferow = str(int(i[1])-1)
        safeL = chr(ord(i[0])-1)+saferow
        safeR = chr(ord(i[0])+1)+saferow
        if safeL in pawns or safeR in pawns:
            safespots.append(i)
    return len(safespots)

まぁ、あとは一々配列に入れて長さを返すんじゃなくて純粋にカウントしてその数字を返すようにするというのでもう少し綺麗にできますね。
とはいえ、一旦満足したので以上でいつも通り幾つかの回答をチェック。
一番評価されてるのは下記になります。
個人的には若干無理に一行にまとめてる感があってあまり好きくない感じです。

def safe_pawns(pawns):
    answer = 0
    for pawn in pawns :
        if chr(ord(pawn[0])-1)+str(int(pawn[1])-1) in pawns or chr(ord(pawn[0])+1)+str(int(pawn[1])-1) in pawns : answer +=1
    return answer

とりあえず、CheckiOは一問解くのに無駄に時間がかかりますね。
まぁ、考えるのも楽しいので良いのですけどね。