Python boot camp by Dr.Angela day8

#Functions
前回に引き続き、Functionについてまた勉強をしていきます。

```python 
def welcome(name):    #Definition function
  print(f"Welcome to my town, {name}.")

welcome("Alex")     #Calling function

出力結果
Welcome to my town, Alex.
```
上記ではname = Alex となっており、(パラメータ) = (実体：Argument)となっている。
L4でCalling functionで呼び出す文字列は「""」で囲むことを忘れないように！

##パラメータを複数実装したいとき
__1. 直接指定しないver.__
→ Calling functionの引数に区切り文字「,」で複数指定可能。

```python
def welcome(location, name):
  print(f"Welcome to {location}, {name}!!")

welcome("George town", "Alexsa")

出力結果
Welcome to George town, Alexsa!!"
```
Calling functionで呼び出すときはパラメータの順序通りに引数を指定する必要がある。今回はlocationが1番目、nameが2番目なので、Calling functionで1番目にGeorge townを指定するとlocationにあてがわれる。

＜引数が複数あって、順番通りに指定するのが面倒orできない場合＞
__2. 直接指定するver.__
→ Calling functionの引数にパラメータと実体を「=」で直接結び付けることで、順序関係なく意図したパラメータに実体を格納することが可能。

```python
def welcome(location, name):
  print(f"Welcome to {location}, {name}!!")

welcome(name="Mary", location="George town")    #各パラメータを「=」で直接指定

出力結果
Welcome to George town, Mary!!"
```

###Mission!!>> Prime Number Checker
Prime number：素数 かどうかを判定するプログラム作成

ぱっと思いついたアルゴリズムとしてはこちら
・numberを2,3,4...と次々に割れるか試して、numberまでに割れる数がなければそいつは素数

まぁ素数かどうかって素因数分解して考えることが一般的なのでアルゴリズムとしてはこれが最適なんじゃないかと思います。

```python:pnc2.py
def PNC():
  number = int(input("Put the number->> "))
  y = 0　         　#割り切れる回数の初期値
  for x in range(1,number+1):
      if number % x == 0 and y < 4:
         y += 1     #割り切れるかつその回数が4回未満ならばインクリメント
      else:
        pass
    if y ==2:       #割り切れる回数が「1とその数自身」の計2回のときが素数
      print(f"{x} is a prime number!!") 
    else:
      print(f"{x} is Not a prime number...")
```
上記の実装時間は__13.859012842...__

__なぜ「y < 4」？__
yが3になるかならないかで繰り返しを続けるかどうか判断できるからです。
「y < 3」だと、yは2までインクリメントを許容するが、それ以上インクリメントするのかしないのかこの時点では判断できず、結果として素数/素数でない数どちらに対してもy=2が成立してしまうためNG。
「y =< 3」でも良さそう。。。(そしてこちらの方が処理速度は3.00000..1～3.9999.....9までの計算をしないので速そう...え、でもrangeで1～number+1までの指定って整数の間の計算て裏でしてるのか？)→ 結果「y <= 3」の実装時間は__12.29203486...で最速__だったのでrangeで整数指定してても実装速度には関係あるっぽいことが証明されました。

でも__もっとシンプルなプログラム__がこちら

```python:pnc3.py
def PNC():
  number = int(input("Put the number->> "))
  for x in range(2,number):
      if number % x == 0:                         #1とそれ自身以外に割り切れる数がある場合
        print(f"{x} is NOT a prime number...")    #それは素数ではない
      else:
        print(f"{x} is a prime number!!")

PNC()
```
そもそもrangeの範囲を1からではなく、2～numberになる一つ前までの数(range()に入れるのはindexなので)を指定することで、1とnumber以外に約数を持つか持たないかで判別可能でしたね。


###プログラムの実装時間の比較 (time.time() / time.perf_counter)
プログラムの実装時間比較をするために各プログラムに追記した部分はこちら

```python
import time
Stime = time.time()

def PNC():
...
...省略

Etime = time.time()
Runtime = Etime - Stime 
print(Runtime)
```
このように実装コードを挟み込む形で記述すると差分の実行時間が得られます。
※ただし、この方法はPythonドキュメントによると1秒より細かい時刻は正確に取得できないそうで、大まかな処理時間の把握にはいいですが、正確に知りたいときは__time.perf_counter__の使用が推奨されます。詳細はこちら→ https://pg-chain.com/python-time-perf_counter

###Mission>> Caesar Cipher (word ver.)
従来の暗号は軍事目的でさまざまなものが考案されましたが、今回はその中でも最も基本となる__アルファベットをずらすことでことが暗号化や解読が簡単に行える__古代ギリシャ人のカエサル・シーザーが考案した「シーザー暗号」をプログラム。詳細はこちら→ https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B6%E3%83%BC%E6%9A%97%E5%8F%B7

```python:cipher.py
key = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']*2

direction = input("Type [encode] or [decode]:\n")
text = input("Type your message:\n").lower()
shift = int(input("Type the shift number:\n"))

def encrypt(plain_text, shift_amount):        #暗号化
  cipher_text = ""
  for letter in plain_text:
    position = key.index(letter)
    new_position = position + shift_amount
    cipher_text += key[new_position]
  print(f"The encoded text is {cipher_text}")

def decrypt(encoded_text, shift_amount):      #復号
  cipher_text = ""
  for letter in encoded_text:
    position = key.index(letter)
    new_position = position - shift_amount
    cipher_text += key[new_position]
  print(f"The original text is {cipher_text}") 

def mode():                                   #モード選択
  if direction == "encode":
    encrypt(text, shift)
  elif direction =="decode":
    decrypt(text, shift)

mode()
```

暗号化のFunctionと復号のFunctionをcaesar()として一つにまとめると・・・

```python:cipher2.py
key = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']*2

direction = input("Type [encode] or [decode]:\n")
text = input("Type your message:\n").lower()
shift = int(input("Type the shift number:\n"))

def caesar(input_text, shift_amount, cipher_direction):   #文章/シフト/モード全て引数に！
  end_text = ""
  for letter in input_text:
    position = key.index(letter)
    if cipher_direction == "decode":                
      shift_amount *= -1                                  #復号なら後ろへシフトなので×-1
    new_position = position + shift_amount
    end_text += key[new_position]
  print(f"The {cipher_direction}d text is {end_text}")

caesar(text, shift, direction)
```
できた！と思ったが、これだと、encodeでhelloとしたときに発生する文字列をdecodeしてもhelloにならない。__なぜか？__
→ mode選択のdirectionがforループ内にあるため、inputした各文字に対してその都度encodeかdecodeを判断し、shift_amountに-1をかけるので、h:暗号化/e:復号/l:暗号化/l:復号/o:暗号化という操作をしているからだ。これを回避するには、directionの判断をfor文の外に出せばよいので、最終的にこのような感じになる。

```python:cipher3.py
key = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']*2                              
#key=[...]*2とすることでとりあえずシフト範囲を広げたが、あくまで応急処置に過ぎない

direction = input("Type [encode] or [decode]:\n")
text = input("Type your message:\n").lower()
shift = int(input("Type the shift number:\n"))

def caesar(input_text, shift_amount, cipher_direction):   #文章/シフト/モード全て引数に！
  end_text = ""
  if cipher_direction == "decode":
    shift_amount *= -1                                    #復号なら後ろへシフトなので×-1
  for letter in input_text:
    position = key.index(letter)
    new_position = position + shift_amount
    end_text += key[new_position]
  print(f"The {cipher_direction}d text is {end_text}")

caesar(text, shift, direction)
```
ここまでは、単語において変換できるようなプログラムを書いてきましたが、暗号は通常__文章__として用いることが多いので・・・
・数字/スペース/記号がある場合でも暗号化してほしい
・もういいよ！というまで繰り返し暗号化/復号をし続けたい
・shift数=26*2以上でも対応できるようにしたい
というわけで、上記3点を満たすようなプログラムへ Let's 改修!!

###Mission>> Caesar Cipher (strings ver.)

```python:cipher4.py
key = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
key2 = ['\"','\'','?','!',',','+','-','*','-','/','#','%','&','(',')','=']

def program():                              #whileを使うためprogram()を新たに定義
    direction = input("Type [encode] or [decode]:\n")
    text = input("Type your message:\n").lower()
    shift = int(input("Type the shift number:\n"))
    shift = shift % 26                      #26で割った余りでシフトを指定することで上限撤廃

    def caesar(input_text, shift_amount, cipher_direction):
        end_text = ""
        if cipher_direction == "decode":
            shift_amount *= -1
        for letter in input_text:
            position = key.index(letter)
            new_position = position + shift_amount
            end_text += key[new_position]
        print(f"The {cipher_direction}d text is {end_text}")
    caesar(text, shift, direction)

program()

while not input("Keep Caesar? [yes/no]\n") == "no":
    program()
else:
    print("THANKS (^^)V")
```
＜改善点＞
・アルファベット26文字で割った余りでシフト数を決めて、シフト数の上限をなくした
・program() を新たに定義し、「no」を入力するまで永遠にprogram()をループさせる

さてさて、最後は数字/記号/スペースに対応させる課題・・・
###Mission>> Caesar Cipher (logo/alphabet/number/symbol)
一番最初にロゴを入れてみたい！のですが、importとfromの違いについて勉強不足で分かっていなかったので、こちらの記事を参照しました。→https://qiita.com/niwaka_dev/items/6e3d9ff6d797243c77c3, https://programming-study.com/technology/python-import/
「__import モジュール名__」
「__from モジュール名 import クラス名(属性)__」
import：__モジュール全体__を指定する
from & import：__モジュールの中の特定の属性(モジュールの一部)__を指定する
→したがって、from & importだと他の使用しないモジュール属性をインストールしなくて済む=重くならない/メモリ節約/何を使用しているかトレースが楽になるといったことがメリットになるわけです。なお、「__from import *__」というワイルドカードを用いた指定は非推奨だそうです。なぜなら、どの名前が名前空間に存在しているかをわかりにくくし、コードの読み手や多くのツールを混乱させるから。モジュール名称が長くタイピングが面倒な場合には、別名(エイリアス)を使うこと！→「__import モジュール名 as 略称__」
※ちなみに、「import ○○ from ××」はエラーになります。

```python:art.py
logo = """           
 ,adPPYba, ,adPPYYba,  ,adPPYba, ,adPPYba, ,adPPYYba, 8b,dPPYba,  
a8"     "" ""     `Y8 a8P_____88 I8[    "" ""     `Y8 88P'   "Y8  
8b         ,adPPPPP88 8PP"""""""  `"Y8ba,  ,adPPPPP88 88          
"8a,   ,aa 88,    ,88 "8b,   ,aa aa    ]8I 88,    ,88 88          
 `"Ybbd8"' `"8bbdP"Y8  `"Ybbd8"' `"YbbdP"' `"8bbdP"Y8 88   
            88             88                                 
           ""             88                                 
                          88                                 
 ,adPPYba, 88 8b,dPPYba,  88,dPPYba,   ,adPPYba, 8b,dPPYba,  
a8"     "" 88 88P'    "8a 88P'    "8a a8P_____88 88P'   "Y8  
8b         88 88       d8 88       88 8PP""""""" 88          
"8a,   ,aa 88 88b,   ,a8" 88       88 "8b,   ,aa 88          
 `"Ybbd8"' 88 88`YbbdP"'  88       88  `"Ybbd8"' 88          
              88                                             
              88           
"""
```
```python:cipher5.py
import string
from art import logo

alphabet = list(string.ascii_lowercase)  
number = list(string.digits)
symbol = list(string.punctuation)

def program():                        #カエサル関数の実行の制御
  print(logo)
  direction = input("Which mode? [encode/decode]\n")
  text = input("Type your message:\n").lower()
  shift = int(input("Type the shift number:\n"))
  
  def caesar(text,shift,direction):   #カエサル関数の中身の計算
    end_text = ""
    a_shift = shift % 26     #alphabet(1-26の計26文字)の余剰
    n_shift = shift % 10     #number(0-9の計10文字)の余剰
    s_shift = shift % 32     #symbol(計32文字)の余剰

    if direction == "decode":   #decodeの際は、逆向きにシフト
      shift *= -1
    for char in text:                    #入力文字列1つ1つの値をつぶさにみていく
      if char in alphabet:               #その値がアルファベットの場合
        position = alphabet.index(char)
        a_position = position + a_shift
        while a_position >= 26:
          a_position -= 26
        end_text += alphabet[a_position]
      if char in number:                  #その値が数字の場合
        position = number.index(char)
        n_position = position + n_shift
        while n_position >= 10:
          n_position -= 10
        end_text += number[n_position]
      if char in symbol:                  #その値が記号の場合
        position = symbol.index(char)
        s_position = position + s_shift
        while s_position >= 32:
          s_position -= 32
        end_text += symbol[s_position]

    print(f"Here's the {direction}d result: {end_text}")
  caesar(text,shift,direction)            #カエサル関数計算の呼び出し

program()                                 #初回カエサル関数実行の呼び出し

while not input("Restart Caesar? [yes/no]\n") == "no":   #noと入れるまで永遠に繰り返す
  program()                               #2回目以降のカエサル関数実行はどうするのか
else:
  print("THANKS, BYE!!(^^♪")
```

これでOKと思ったが、アルファベットと記号に関してdecodeしても平文に戻らないwww
調べてみると、どうやら負の数の余剰に関して以下の現象を考えなくてはならなかった。
100  % 26 = 22 →  100 ÷ 26 =  3...+22
-100 % 26 = 4  → -100 ÷ 26 = -4...+4
普通に計算したら下段は __-100 % 26 = -3...-22__になると思っていたのだが。。。なんか違うっぽいwwで、こいつが原因でアルファベットと記号に関してずれが生じている？？あ、今回100は10の倍数なので10で割る数字にずれが生じていないだけで、数字に関しても他の値を入れたらズレが生じたwww
__解決策>>__ pythonでは負の数は厳密に定義されていないので、負の数を割るのではなく、結果に－を付与する方向で解決する。よってcipher5.pyの一部を以下のように改変して終了！

```python:answer.py
#...def caesar()以前は省略...

  def caesar(text,shift,direction):   #カエサル関数の中身の計算
    end_text = ""
    a_shift = shift % 26     #alphabet(1-26の計26文字)の余剰
    n_shift = shift % 10     #number(0-9の計10文字)の余剰
    s_shift = shift % 32     #symbol(計32文字)の余剰

    if direction == "decode":   #decodeの際は、逆向きにシフト
     #shift *= -1  は削除        
      a_shift *= -1          #計算前の値ではなく、計算後の値にマイナスを付与
      n_shift *= -1
      s_shift *= -1 
```