AOJ 「ITP1_11」をpythonで解く（サイコロのクラスを作る）

#初めに
AOJで[ITP](https://onlinejudge.u-aizu.ac.jp/courses/lesson/2/ITP1/11)を解いていたら、たまたま「[【競プロ初心者向け】AOJ 「ITP I」40問をpythonで解いてみた](https://qiita.com/cmtennis1042/items/5f1e7f071081176e857f)」という記事を見つけ、せっかくだし補完と記録的な意味合いで書くことにしました。（この記事は41~44問目にあたる）

自分なりに汎用性を持たせることをイメージしてクラスを書きましたが、もっと効率的な書き方や怪しい記述があれば指摘していただきたいです。（筆者は初心者なので一般的な書き方をしていない可能性があります...）



#  今回作ったサイコロ
```python
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
```


##機能の説明
####コンストラクタ
```python
# 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
```python
# from class Dice
    def turn_e(self): # 右に転がる
        self.__top, self.__lft, self.__btm, self.__rit = \
        self.__lft, self.__btm, self.__rit, self.__top
```
４つあります。
転がしたときの値を入れ替えて状態を変えています。
「```turn_```〇〇」となっていますが、〇〇には方角の頭文字が入ります。奥方向なら「n」です。

　　
####回転*2
```python
# from class Dice
    def spin_r(self): # 右回転 
        self.__rit, self.__fro, self.__lft, self.__bak = \
        self.__bak, self.__rit, self.__fro, self.__lft
```
２つあります。
実は今回の問題では直接使わないんですが、いずれ他の問題で使うかもしれないと思って一応実装してます。

　　
#### 同じように置かれているか
```python
# 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```を返します。

実行するときは、

```python
dice = Dice([1,2,3,4,5,6])
dice.is_same_setting([6,5,4,3,2,1])
```
のように使います。下で引数になっているのが、比較したいサイコロです。

　　
####回転させて同じダイスになるか
```python
# 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```した点での置き方に変わってしまうので編集するときは注意してください。

実行するときは、

```python
dice = Dice([1,2,3,4,5,6])
dice.is_same_dice([6,2,4,3,5,1]) # True
```
のように使います。
　
####出目の表示
```python
# from class Dice
    def show_top(self): # 上面の値を表示
        return self.__top
```
みたまんまです。他の面を表示したいときは各自でお願いします。
今回の問題はこれしか使いません。


　

それでは、実際に解いていきます。
#  ITP1_11_A サイコロ1

サイコロを指示された通り転がし、転がし終えた後の上面の値を答える問題です。
######解答
```python
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)```を少し変更してあげればよさそうです。

```python
    # 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```を返すだけです。

######解答
```python
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を出力する問題です。
回転させて同じかどうかは一発で求められます。
######解答
```python
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
サイコロがｎ個与えられるので、それらがすべて異なるサイコロならYes、一つでも同じサイコロがあればNoを出力する問題です。

```n <= 100```なので、愚直に全組み合わせについて調べても間に合いそうです。
######解答
```python
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でこのようにサイコロを扱った記事は、ぱっと見た感じでは無かったと思います。（多分）
このテンプレートの差別化点としては、
・自分で目を設定できる
・置き方全列挙がある
ぐらいでしょうか。
なんにせよ、これでサイコロ転がす系の問題は楽勝です！~~（サイコロの問題そんなに多くない気もしているんですけどね！）~~



至らないところも多かったと思いますが、少しでも誰かの参考になれたらうれしいです。