初めに
AOJでITPを解いていたら、たまたま「【競プロ初心者向け】AOJ 「ITP I」40問をpythonで解いてみた」という記事を見つけ、せっかくだし補完と記録的な意味合いで書くことにしました。(この記事は41~44問目にあたる)
自分なりに汎用性を持たせることをイメージしてクラスを書きましたが、もっと効率的な書き方や怪しい記述があれば指摘していただきたいです。(筆者は初心者なので一般的な書き方をしていない可能性があります...)
今回作ったサイコロ
class Dice:
def __init__(self, ary): # [top, front, right, left, back, bottom]
self.__top = ary[0]
self.__fro = ary[1]
self.__rit = ary[2]
self.__lft = ary[3]
self.__bak = ary[4]
self.__btm = ary[5]
def turn_e(self): # 右に転がる
self.__top, self.__lft, self.__btm, self.__rit = \
self.__lft, self.__btm, self.__rit, self.__top
def turn_s(self): # 手前に転がる
self.__top, self.__fro, self.__btm, self.__bak = \
self.__bak, self.__top, self.__fro, self.__btm
def turn_w(self): # 左に転がる
self.__top, self.__lft, self.__btm, self.__rit = \
self.__rit, self.__top, self.__lft, self.__btm
def turn_n(self): # 奥に転がる
self.__top, self.__fro, self.__btm, self.__bak = \
self.__fro, self.__btm, self.__bak, self.__top
def spin_r(self): # 右回転
self.__rit, self.__fro, self.__lft, self.__bak = \
self.__bak, self.__rit, self.__fro, self.__lft
def spin_l(self): # 左回転
self.__rit, self.__fro, self.__lft, self.__bak = \
self.__fro, self.__lft, self.__bak, self.__rit
def is_same_setting(self, ary): # 同じように置いているか
if self.__top == ary[0] and self.__fro == ary[1] and self.__rit == ary[2] and \
self.__lft == ary[3] and self.__bak == ary[4] and self.__btm == ary[5]:
return True
def is_same_dice(self, ary): # 回転させて同じダイスになるか
is_same = False
for _ in range(2):
for _ in range(3):
for _ in range(4):
if self.is_same_setting(ary):
is_same = True
self.spin_r()
self.turn_n()
self.spin_r()
self.turn_s()
return is_same
def show_top(self): # 上面の値を表示
return self.__top
機能の説明
コンストラクタ
# from class Dice
def __init__(self, ary): # [top, front, right, left, back, bottom]
self.__top = ary[0]
self.__fro = ary[1]
self.__rit = ary[2]
self.__lft = ary[3]
self.__bak = ary[4]
self.__btm = ary[5]
各面の値の配列を引数にとります。例えば、普通のサイコロなら [1, 2, 3, 4, 5, 6]
です。
インスタンス生成時の引数は必ず、[top, front, right, left, back, bottom]
の順にしてください。
転がし方*4
# from class Dice
def turn_e(self): # 右に転がる
self.__top, self.__lft, self.__btm, self.__rit = \
self.__lft, self.__btm, self.__rit, self.__top
4つあります。
転がしたときの値を入れ替えて状態を変えています。
「turn_
〇〇」となっていますが、〇〇には方角の頭文字が入ります。奥方向なら「n」です。
回転*2
# from class Dice
def spin_r(self): # 右回転
self.__rit, self.__fro, self.__lft, self.__bak = \
self.__bak, self.__rit, self.__fro, self.__lft
2つあります。
実は今回の問題では直接使わないんですが、いずれ他の問題で使うかもしれないと思って一応実装してます。
同じように置かれているか
# from class Dice
def is_same_setting(self, ary):
if self.__top == ary[0] and self.__fro == ary[1] and self.__rit == ary[2] and \
self.__lft == ary[3] and self.__bak == ary[4] and self.__btm == ary[5]:
return True
2つのサイコロを比較して、各面の値がすべて一致しているかの判定をしてくれます。
向きも一致していないといけないので、同じサイコロを同じ置き方(6がbottomで、5がfrontになるように置く、など)をしたときのみTrue
を返します。
実行するときは、
dice = Dice([1,2,3,4,5,6])
dice.is_same_setting([6,5,4,3,2,1])
のように使います。下で引数になっているのが、比較したいサイコロです。
回転させて同じダイスになるか
# from class Dice
def is_same_dice(self, ary):
is_same = False
for _ in range(2):
for _ in range(3):
for _ in range(4):
if self.is_same_setting(ary):
is_same = True
self.spin_r()
self.turn_n()
self.spin_r()
self.turn_s()
return is_same
今度は、2つのサイコロを比較して、各面の値が回転して一致するかの判定をしてくれます。
インスタンスで作ったサイコロを実際に回転させて、与えた引数(比べたいサイコロ)と一致していれば、True
を返します。
この関数で行っている処理は、置き方の全列挙です。
サイコロの置き方は24通りしかないので、上のfor文のループ回数2 * 3 * 4 = 24
ですべてカバーできています。
また、サイコロの回転を複数回行っていますが、この処理の前後でサイコロの置き方は変化しません。
ただ、このfor文をbreak
したりすると、break
した点での置き方に変わってしまうので編集するときは注意してください。
実行するときは、
dice = Dice([1,2,3,4,5,6])
dice.is_same_dice([6,2,4,3,5,1]) # True
のように使います。
出目の表示
# from class Dice
def show_top(self): # 上面の値を表示
return self.__top
みたまんまです。他の面を表示したいときは各自でお願いします。
今回の問題はこれしか使いません。
それでは、実際に解いていきます。
ITP1_11_A サイコロ1
サイコロを指示された通り転がし、転がし終えた後の上面の値を答える問題です。
解答
surfaces = list(map(int,input().split()))
instructions = list(input())
dice = Dice(surfaces)
for inst in instructions:
if inst == "E":
dice.turn_e()
if inst == "N":
dice.turn_n()
if inst == "S":
dice.turn_s()
if inst == "W":
dice.turn_w()
print(dice.show_top())
言われた通り実装します。
ITP1_11_B サイコロ2
サイコロを回転させた後のtop
とfront
の値が与えられるので、その時のright
を出力する問題です。
この問題は置き方の全列挙を使えば、簡単に右側面の値が求まるはずです。
なので、is_same_dice(self)
を少し変更してあげればよさそうです。
# def is_same_dice(self)を一部変更
def top_fro_right(self, ary): +++
right = 0 +++
for _ in range(2):
for _ in range(3):
for _ in range(4):
if self.is_same_top_fro(ary): +++
right = self.__rit +++
self.spin_r()
self.turn_n()
self.spin_r()
self.turn_s()
return right +++
def is_same_top_fro(self, ary): +++
if ary[0] == self.__top and ary[1] == self.__fro: +++
return True +++
この問題に合わせた条件式を新たに作っています。
この条件式はtop
とfro
が一致したときにTrue
を返すだけです。
解答
surfaces = list(map(int,input().split()))
q = int(input())
dice = Dice(surfaces)
for _ in range(q):
top_fro = list(map(int,input().split()))
right = dice.top_fro_right(top_fro)
print(right)
こんな感じでよさそうです。
このように、条件式を工夫すれば色々な問題に対応できると思います。
ITP1_11_C サイコロ3
2つのサイコロが一致していればYes、不一致ならNoを出力する問題です。
回転させて同じかどうかは一発で求められます。
解答
surfaces1 = list(map(int,input().split()))
surfaces2 = list(map(int,input().split()))
dice = Dice(surfaces1)
if dice.is_same_dice(surfaces2):
print('Yes')
else:
print('No')
簡単!
ITP1_11_D サイコロ4
サイコロがn個与えられるので、それらがすべて異なるサイコロならYes、一つでも同じサイコロがあればNoを出力する問題です。
n <= 100
なので、愚直に全組み合わせについて調べても間に合いそうです。
解答
n = int(input())
surfaces_stack = [None] * n
for i in range(n):
surfaces_stack[i] = list(map(int,input().split()))
for i in range(n-1):
for j in range(i+1, n):
dice = Dice(surfaces_stack[i])
if dice.is_same_dice(surfaces_stack[j]):
print('No')
break
else:
continue
break
else:
print('Yes')
こんな感じでしょうか。
同じサイコロがあれば、Noを出力してすべてのfor文からbreak
し、一度もbreak
しなければYesを出力します。
これですべての問題が終わりました。
最後に
ここまで読んでくださってありがとうございました。
以前某サイトで初めてサイコロの問題を解いたときに、回転をどう表せばいいのか非常に悩んだ覚えがあります。
やってることは単純なのですが、4方向の回転の処理を書いていくのはなかなか骨が折れます。
なにかテンプレート的なものがあれば簡単に解けるのにな、ということで考えてみました。
pythonでこのようにサイコロを扱った記事は、ぱっと見た感じでは無かったと思います。(多分)
このテンプレートの差別化点としては、
・自分で目を設定できる
・置き方全列挙がある
ぐらいでしょうか。
なんにせよ、これでサイコロ転がす系の問題は楽勝です!(サイコロの問題そんなに多くない気もしているんですけどね!)
至らないところも多かったと思いますが、少しでも誰かの参考になれたらうれしいです。