2024/4/15 地域に密着「ローカル防災アプリ」で災害に備えよう を更新しました

日々の安心した生活は防災から!
「もしも」に備えるための防災グッズやアプリ、お役立ち情報などを発信中!

Pythonで火山カメラ画像をキャプチャする(3)

Python

こんにちは、管理人のアカツキです。新たに「副産物」カテゴリでプログラミング言語Pythonを用いた自作ツールの紹介を行っています。

第三回目となる今回は、火山カメラキャプチャについて、その処理部分の解説を行います。

スポンサーリンク

(再掲)火山カメラ・キャプチャ コード

例によりまして、火山キャプチャの全体コードとその機能について再掲します。

"""
JMA火山監視カメラキャプチャ Ver.1.01
・JMAの火山監視カメラ画像をキャプチャする
・連続キャプチャを実施する(重複はスキップ)
・火山名をリストから選択する
・最初のリストはよくアクセスすると思われる火山
・火山リストをアップデート(20220727更新分)
"""
# ブロック1
from selenium import webdriver
from selenium.webdriver.chrome import service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import os
import requests
import time

# ブロック2
dir_path = "(適当なディレクトリ)/volcano_camera/"

fav_volcano = {1:'十勝岳 白金模範牧場',
               2:'阿蘇山 草千里',
               3:'桜島 牛根',
               4:'諏訪之瀬島 寄木',
               5:'その他'}

area_volcano = {1:'北海道',
                2:'東北地方',
                3:'関東・中部地方',
                4:'伊豆・小笠原諸島',
                5:'九州地方',
                6:'戻る'}

hokkaido_volcano = {1: 'アトサヌプリ 北東山麓', 2: 'アトサヌプリ 硫黄山駐車場北',
                    3: 'アトサヌプリ 屈斜路湖南', 4: '雌阿寒岳 上徹別',
                    5: '雌阿寒岳 阿寒富士北', 6: '大雪山 忠別湖東',
                    7: '大雪山 旭岳姿見2', 8: '十勝岳 白金模範牧場',
                    9: '十勝岳 避難小屋南東', 10: '樽前山 別々川',
                    11: '倶多楽 414m山', 12: '倶多楽 地獄谷',
                    13: '有珠山 東湖畔', 14: '有珠山 月浦',
                    15: '北海道駒ヶ岳 鹿部公園南東', 16: '北海道駒ヶ岳 赤井川',
                    17: '北海道駒ヶ岳 剣ヶ峯', 18: '恵山 高岱',
                    19: '恵山 火口原', 20: '戻る'}

tohoku_volcano = {1: '岩木山 百沢東', 2: '八甲田山 大川原', 3: '八甲田山 地獄沼',
                  4: '十和田 銀山', 5: '秋田焼山 栂森', 6: '岩手山 柏台',
                  7: '岩手山 黒倉山', 8: '鳥海山 上郷2', 9: '栗駒山 大柳',
                  10: '栗駒山 展望岩頭', 11: '蔵王山 遠刈田温泉', 12: '蔵王山 上山金谷',
                  13: '蔵王山 刈田岳', 14: '蔵王山 御釜北', 15: '吾妻山 上野寺',
                  16: '安達太良山 若宮', 17: '安達太良山 鉄山', 18: '磐梯山 剣ケ峯',
                  19: '磐梯山 櫛ヶ峰', 20: '戻る'}

kanto_tyubu_volcano = {1: '那須岳 湯本ツムジケ平', 2: '那須岳 日の出平北', 3: '日光白根山 歌ヶ浜',
                       4: '日光白根山 上小川', 5: '草津白根山 奥山田', 6: '草津白根山 逢ノ峰山頂',
                       7: '草津白根山 草津', 8: '浅間山 鬼押', 9: '浅間山 追分',
                       10: '新潟焼山 宇棚', 11: '弥陀ヶ原 芦峅', 12: '焼岳 中尾峠',
                       13: '乗鞍岳 乗鞍高原', 14: '御嶽山 三岳黒沢', 15: '御嶽山 奥の院',
                       16: '白山 白峰', 17: '富士山 萩原', 18: '箱根山 宮城野',
                       19: '箱根山 大涌谷', 20: '箱根山 箱根峠', 21: '伊豆東部火山群 大原',
                       22: '伊豆東部火山群 大崎', 23: '戻る'}

izu_ogasawara_volcano = {1: '伊豆大島 北西外輪', 2: '伊豆大島 中央火孔北', 3: '新島 式根',
                         4: '神津島 前浜南東', 5: '三宅島 坪田', 6: '三宅島 神着',
                         7: '三宅島 山頂火口北西', 8: '八丈島 楊梅ヶ原', 9: '青ヶ島 手取山', 
                         10: '戻る'}

kyusyu_volcano = {1: '鶴見岳・伽藍岳 塚原無田', 2: '九重山 上野', 3: '九重山 星生山北尾根',
                  4: '九重山 飯田大原', 5: '阿蘇山 草千里', 6: '阿蘇山 車帰',
                  7: '阿蘇山 南阿蘇村', 8: '雲仙岳 野岳', 9: '雲仙岳 垂木台地南',
                  10: '霧島山 猪子石(新燃岳)', 11: '霧島山 猪子石(御鉢)', 12: '霧島山 御鉢火口南縁',
                  13: '霧島山 高原西麓', 14: '霧島山 八久保', 15: '霧島山 韓国岳',
                  16: '霧島山 えびの高原', 17: '霧島山 硫黄山南', 18: '桜島 牛根',
                  19: '桜島 東郡元', 20: '桜島 垂水荒崎', 21: '桜島 中央港新町',
                  22: '薩摩硫黄島 岩ノ上', 23: '口永良部島 本村西', 24: '口永良部島 屋久島吉田',
                  25: '諏訪之瀬島 寄木', 26: '諏訪之瀬島 キャンプ場', 27:'戻る'}

area_volcano2 = {'北海道':hokkaido_volcano,
                '東北地方':tohoku_volcano,
                '関東・中部地方':kanto_tyubu_volcano,
                '伊豆・小笠原諸島':izu_ogasawara_volcano,
                '九州地方':kyusyu_volcano}

# ブロック3
# メニューを行き来するためのフラグを設定
Flag = True
while Flag:
    for vkey1, vvalue1 in fav_volcano.items():
        print(f"{vkey1} : {vvalue1}")
    
    select_vkey1 = input("火山を選択してください [No.?] >>> ")
    select_vkey1 = int(select_vkey1)
    select_vvalue1 = fav_volcano[select_vkey1]
    
    # 1~6を選んだ場合の処理→値を取得してループ離脱
    if not select_vvalue1 == "その他":
        select_volcano = select_vvalue1
        break
    
    elif select_vvalue1 == "その他":
        
        # もう一つwhileを入れる
        while Flag:
            for vkey2, vvalue2 in area_volcano.items():
                print(f"{vkey2} : {vvalue2}")

            select_vkey2 = input("地域を選択してください [No.?] >>> ")
            select_vkey2 = int(select_vkey2)
            select_vvalue2 = area_volcano[select_vkey2]
            
            if select_vvalue2 == "戻る":
                # 再度前の画面に戻る
                break

            elif select_vvalue2 != "戻る":
                for vkey3,vvalue3 in area_volcano2[select_vvalue2].items():
                    print(f"{vkey3} : {vvalue3}")
             
                select_vkey3 = input("火山を選択してください [No.?] >>> ")
                select_vkey3 = int(select_vkey3)
                select_vvalue3 = area_volcano2[select_vvalue2][select_vkey3]
                
                if not select_vvalue3 == "戻る":
                    # 火山名を取得してループを抜ける
                    select_volcano = select_vvalue3
                    Flag = False

                elif select_vvalue3 == "戻る":
                    # 地域選択画面に戻る
                    pass

print(select_volcano,"を選びました")
print(select_volcano,"にアクセスします")

# ブロック4
chrome_driver = "(適当なディレクトリ)/chromedriver.exe"
chrome_service = service.Service(executable_path=chrome_driver)
driver = webdriver.Chrome(service=chrome_service)
wait = WebDriverWait(driver=driver, timeout=10)

driver.implicitly_wait(5)
driver.set_window_position(50,50)
driver.set_window_size(1400,1300)

# JMAの監視カメラ画像のページに直接アクセスする
driver.get("https://www.data.jma.go.jp/vois/data/tokyo/volcam/volcam.php")
# wait.until(EC.presence_of_all_elements_located)

# 選択された火山カメラを開く
volcam = driver.find_element(By.LINK_TEXT,select_volcano)
volcam.click()

# wait.until(EC.presence_of_all_elements_located)

# スライダを左端に移動
driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/oldest.png']").click()

# 早送りボタンを取得
forward = driver.find_element(By.CSS_SELECTOR,"img[src='./icon/player/next-frame.png']")

# ブロック5
# 画像を保存する(繰り返し)
# 撮影枚数を取得
img_num = driver.find_element(By.XPATH,"//*[@id='main']/form/table[2]/tbody/tr[2]/td/table/tbody/tr[3]/td[5]/input[3]")
img_num = img_num.get_attribute("value")
time.sleep(1)

for i in range(int(img_num)):
    img_tag = driver.find_element(By.NAME,"myImg")
    img_url = img_tag.get_attribute("src")
    
    img = requests.get(img_url)
    file_name = os.path.basename(img_url)
    
    # 保存ディレクトリの作成
    # 取得した火山名を使う
    save_dir = os.path.join(dir_path,select_volcano)
    
    # if not文で判断→フォルダがない→False→if notはTrue→フォルダを作る
    if not os.path.isdir(save_dir):
        os.makedirs(save_dir)
    
    # 撮影の重複をチェック
    check_file = os.path.join(save_dir,file_name)
    
    # if notで判定する
    # 条件が真ならif notは偽を返す
    # 条件が偽ならif notは真を返す
    # ファイルがない=撮影していない=if notがTrue=Trueの処理=キャプチャする
    if not os.path.isfile(check_file):
        with open(check_file,"wb") as f:
            f.write(img.content)
    
    # 次の時間に移動
    forward.click()
    time.sleep(1)

driver.quit()
print("終わりました")

またプログラムの機能は次のようになっています。

JMA火山監視カメラキャプチャ Ver.1.01(volcano_capture.py)

  • 気象庁(JMA)が提供している火山監視カメラ画像について、
    任意の火山画像をキャプチャ(保存)する
  • キャプチャする火山名はリストから選択する(番号を入力して指定)
  • 画像を保存するフォルダ・ファイル名は自動で生成する
  • 火山リストを更新(2022.7.27 カメラ追加のプレスリリース)

また、プログラムはその役割ごとにブロックに分割しています。

ブロック一覧とその役割

  1. プログラムで使用するモジュールの呼び出し
  2. ファイル保存や火山名の選択に使う変数やデータ
  3. 火山選択メニュー
  4. 選択された火山カメラをブラウザで読み込む
  5. キャプチャした画像の保存処理

今回はブロック3について解説を進めていきます。

ブロック3・火山選択メニュー

# ブロック3
# メニューを行き来するためのフラグを設定
Flag = True
while Flag:
    for vkey1, vvalue1 in fav_volcano.items():
        print(f"{vkey1} : {vvalue1}")
    
    select_vkey1 = input("火山を選択してください [No.?] >>> ")
    select_vkey1 = int(select_vkey1)
    select_vvalue1 = fav_volcano[select_vkey1]
    
    # 1~6を選んだ場合の処理→値を取得してループ離脱
    if not select_vvalue1 == "その他":
        select_volcano = select_vvalue1
        break
    
    elif select_vvalue1 == "その他":
        
        # もう一つwhileを入れる
        while Flag:
            for vkey2, vvalue2 in area_volcano.items():
                print(f"{vkey2} : {vvalue2}")

            select_vkey2 = input("地域を選択してください [No.?] >>> ")
            select_vkey2 = int(select_vkey2)
            select_vvalue2 = area_volcano[select_vkey2]
            
            if select_vvalue2 == "戻る":
                # 再度前の画面に戻る
                break

            elif select_vvalue2 != "戻る":
                for vkey3,vvalue3 in area_volcano2[select_vvalue2].items():
                    print(f"{vkey3} : {vvalue3}")
             
                select_vkey3 = input("火山を選択してください [No.?] >>> ")
                select_vkey3 = int(select_vkey3)
                select_vvalue3 = area_volcano2[select_vvalue2][select_vkey3]
                
                if not select_vvalue3 == "戻る":
                    # 火山名を取得してループを抜ける
                    select_volcano = select_vvalue3
                    Flag = False

                elif select_vvalue3 == "戻る":
                    # 地域選択画面に戻る
                    pass

print(select_volcano,"を選びました")
print(select_volcano,"にアクセスします")

処理の流れを図で整理

このブロックでは、ユーザーがどの火山カメラにアクセスするのかを決定する処理を行っています。

具体的には、ユーザー側が入力した番号から対応する火山名を取得するようになっており、ブロック2で作成した辞書が大いに活躍します。このような辞書を作っていましたね。

  • fav_volcano
    よくアクセスする火山をまとめたもの
  • area_volcano
    任意の火山を選ぶときに地域の絞り込みを行うための地域一覧
  • hokkaido_volcano, tohoku_volcano, …, kyusyu_volcano
    各地域でカメラ提供されている火山一覧
    (hokkaido_volcanoなら北海道の火山といった具合です)
  • area_volcano2
    ブロック3の内部処理用
    キーを番号ではなく地域名にして値を各地域の火山一覧にしたもの
    (辞書に辞書をネストしている)

そのような訳でさっそくコードを見ていきたいところですが、実はこのブロック、少し処理が複雑になっています。まずは処理の流れを確認するため、フロー図を見てみることにしましょう。

ブロック3のフロー図
ブロック3のフロー図

こちらがブロック3の処理をまとめたフロー図になります。結構ごちゃごちゃしていますが、見晴らしてみますと主な処理は四角の処理ひし形の処理セットになっていることが見えてくるかと思います。

特にひし型は条件分岐を示しており、上流の四角の処理で得られた値の内容で処理の行き先を変えています。

そしてこのセットが合計で三つあります。では各セットではどのような処理を行っているのか?ということですが、いずれもselect_vkeyからselect_vvalueの値を取得しています。細かく番号が付いていますが本質は同じです。整理しますと

  1. select_vkey
    volcano(火山)のkey(キー)を選ぶ(select)
    →ユーザーが番号を入力して指定する
  2. select_vvalue
    select_vkeyから対応する値(value)を取得する
    →火山辞書を参照し、キーから値を取得する

となります。つまりユーザーがアクセスしたい火山をここで決定しています。各セットでは

  1. select_vkey1select_vvalue1
    お気に入りリストであるfav_volcanoの中から火山を選ぶ
  2. select_vkey2とselect_vvalue2
    お気に入り以外から火山を選びたい場合、地域を選択する(area_volcano)
  3. select_vkey3とselect_vvalue3
    選択した地域に対応した火山リストから火山を選択する(hokkaido_volcano,…)

のように選択の段階を設けており、最終的にリストにあるすべての火山から選択できるようにしています。したがってブロック3は火山選択メニューという役割を持っていることになります。

for文で一覧を表示

シーケンスから要素を取り出します

まずブロック3の全体の流れを解説しました。フロー図にはまだ気になる部分、赤線緑線がありますがこれは次回の記事でやっていきます。

まずユーザー側がどのようにアクセスしたい火山を決定していくのか、その部分を見ていきましょう。

for vkey1, vvalue1 in fav_volcano.items():
    print(f"{vkey1} : {vvalue1}")

select_vkey1 = input("火山を選択してください [No.?] >>> ")
select_vkey1 = int(select_vkey1)
select_vvalue1 = fav_volcano[select_vkey1]

コードの前にwhile文がありwhileブロックになっているので実際にはインデントが入っていますがここでは省略しています。以下同様にインデントを省略していることがあることにご注意ください。

さて、5行目です。

for vkey1, vvalue1 in fav_volcano.items():

ここではfor文によって繰り返し処理を行っています。

# for文 繰り返し処理を行う
for 繰り返し変数 in シーケンス:
    処理内容

# チーム名を表示
tokkou_yarou = ["alpha", "bravo", "charlie", "delta", "echo"]
for team in tokkou_yarou:
    print(f"私たちは 特攻野郎 {team} チームです!")

# 私たちは 特攻野郎 alpha チームです!
# 私たちは 特攻野郎 bravo チームです!
# 私たちは 特攻野郎 charlie チームです!
# 私たちは 特攻野郎 delta チームです!
# 私たちは 特攻野郎 echo チームです!

# f文字列
# { }を使うことで文章の中に変数を差し込める
# Python3.6から追加された機能
# 同等の機能はformatメソッドでも可能だが、こちらがより使いやすいかも

# 先のprint内をformatメソッドで書き換えると...
tokkou_yarou = ["alpha", "bravo", "charlie", "delta", "echo"]
for team in tokkou_yarou:
    print("私たちは 特攻野郎 {} チームです!".format(team))

このように、forの後に繰り返し変数を指定し、シーケンス(複数の要素が順番に入ったデータ型。リストやタプル、range型)をシーケンス内の要素を調べるinの後に置くことで、その要素が一つずつ取り出されて繰り返し変数に代入されます。

その一つずつ取り出された要素についての処理がfor以下になります。上の例でも一つずつリストの値が取り出されていますね。

また、print文の文章にはf文字列という書き方を使って変数を差し込んでいます。formatメソッドと同等の機能ですが、たとえば数字の桁を揃えたりもできて非常に応用範囲が広いです。ここではごく基本的な使い方になっていると思います。

for文で辞書型を使うには?

以上を踏まえた上で再度5行目以下のコードを見てみましょう。

for vkey1, vvalue1 in fav_volcano.items():
    print(f"{vkey1} : {vvalue1}")

先ほどのfor文の解釈からすると、このコードは、辞書fav_volcanoの値とキーを一つずつ取り出し、それぞれvkey1vvalue1に代入しなさい、という処理になりそうですね。ここで繰り返し変数が2つになっていますが、別に1つという決まりはなく、複数書くことも可能です。

しかし、for文の決まりを考えるに、inの後に辞書は使えないのではなかったでしょうか。というのも辞書型は順番というものがなく、シーケンスとは呼べないからです。

そこで改めてinの後を見てみます。するとfav_volcano.items()がくっ付いていることがわかります。
このitemsメソッドが一つのポイントになっています。

itemsメソッドは辞書からキーと値のペアを取り出し、タプルにしたリストを返してくれます。リストはシーケンスでしたから、fav_volcano.items()inの後に置くことができるという訳です。

そしてリストの各要素はタプルになっているのですから、一回の取り出しにつき二つの要素(キーと値)を変数に入れないといけません。これが繰り返し変数にvkey1vvalue1の二つが指定されている理由になります。コードを実行した結果がこちらです。

# 1 : 十勝岳 白金模範牧場
# 2 : 阿蘇山 草千里
# 3 : 桜島 牛根
# 4 : 諏訪之瀬島 寄木
# 5 : その他

きちんと火山リストが表示されていますね。

入力された値から火山名を取り出す

for vkey1, vvalue1 in fav_volcano.items():
    print(f"{vkey1} : {vvalue1}")

select_vkey1 = input("火山を選択してください [No.?] >>> ")
select_vkey1 = int(select_vkey1)
select_vvalue1 = fav_volcano[select_vkey1]

再びコードの冒頭部分です。今まで上の2行について述べてきましたが、これによって火山リストが表示されることがわかりました。となると次はそのリストから火山を選ぶ作業になります。それが後半3行のコードになります。

inputで値を入力

select_vkey1 = input("火山を選択してください [No.?] >>> ")

こちらからPythonに値を教えるにはinputを用います。これを実行することでPythonは入力値をselect_vkey1に渡します。

上のようにカッコ内に文字列を入れてあげることで、どのような値が必要かというのを示すことができます(ここではリスト内の数字を入力してほしいので、No.?とハテナの部分、つまり数字を入力すれば良いのだな、という案内をしているつもりです)。

これでselect_vkey1に値が入るのですが、これをint関数で整数値に変換しておきます。その値を再度select_vkey1に入れて書き換えています。

select_vkey1 = int(select_vkey1)

キーが取得できましたので、対応する値を取り出します。

select_vvalue1 = fav_volcano[select_vkey1]

辞書型データでは、[ ]にキーを指定することで値を取り出せましたね。これをselect_vvalue1に格納します。ここまでが最初のお気に入りリストから火山名を取り出す過程になります。

ユーザーに選択させる部分は後二か所ありますが(地域選択とその地域内の火山選択)、基本的にはここと同じ構文になっており、変数が変わっているだけになります。

ただ、地域内の火山選択については少し構文が違っています。

select_vvalue3 = area_volcano2[select_vvalue2][select_vkey3]

area_volcano2はネストされた辞書データですから、area_volcano2[select_vvalue2]が選択地域の火山辞書になっています。

それに対してユーザーが選んだ番号がselect_vkey3ですので、area_volcano2[select_vvalue2]に対して[select_vkey3]を作用させます。[ ]が二回使われていることに注意が必要です。

条件分岐を実装する(if文)

ここまで火山を選択し、辞書から値を取り出してそれを変数(select_vvlaue)に格納する流れを説明しました。これはフロー図にある三つの四角+ひし形セット処理の内、四角の部分に当たります。フロー図を再度お示ししておきます。

ブロック3のフロー図
ブロック3のフロー図(再掲)

そして四角の処理で得られた値について、ひし形部分で条件分岐をすることになります。
各条件分岐の判定は次のようになっています。

  • select_vvalue1(お気に入りリストから選んだ火山)の値
    →「その他」ならば地域選択リストを表示する(Yes)
    →「その他以外」であればブロック4の処理に進む(No)
  • select_vvalue2(地域選択リストから選んだ地域)の値
    →「戻る」が選択されていたら再度お気に入りリストを表示する(Yes)
    →「地域」が選択されていたらその地域の火山リストを表示する(No)
  • select_vvalue3(地域火山リストから選んだ火山)の値
    →「戻る」が選択されていたら地域選択リストを表示する(Yes)
    →「火山」が選択されていたらブロック4の処理に進む(No)

ちょっとややこしいですが、ここの分岐が重要です。いずれも基本的な構文は変わりませんのでselect_vvalue1の場合を例にして見ていきましょう。

select_vvalue1には、お気に入りリストから選んだ値が格納されています。このリストは辞書fav_volcanoに入っており、番号1~4が火山名、そして5がその他になっています。

もしここで1~4、つまり火山名が選ばれたなら、その時点でブロック3の目的である火山選択が達成されたことになります。そのため、フロー図で示すところの右側の処理に移り、ブロック4に進むという訳です。

一方で、お気に入りリストに見たい火山が無い場合(5、その他)は地域の火山から選ぶことになります。そのためにはまず地域を選ぶ必要がありますから、フロー図の直下の処理に進みます。それらの処理を示したのが以下のコードです。

if not select_vvalue1 == "その他":
    select_volcano = select_vvalue1
    break
elif select_vvalue1 == "その他":

# if文 条件によって処理を変える
if 条件式:
    処理内容

# 条件式が複数あり、それぞれについて別の処理をしたい場合はelif文を使う
if 条件式1:
    条件式1の時に行う処理内容
elif 条件式2:
    条件式2の時に行う処理内容

もしこういう条件なら、こういった処理をしたい…という場合分けに使う構文としてif文があります。if(もしも)ということで、ストレートな文ですね。

そしてここでは場合分け条件が二つありました。火山名が選択された場合と、その他の場合ですね。

しかしちょっと待ってください。

確かお気に入りリストは1~5まで番号があったはずです。つまりこれは五択ということになって条件は二つではなく五つではないか?そう思われるかも知れません。となるとif文は五つ必要になるのでしょうか。

いいえ、そんなことはありません。いちいちif文で条件を分けていたらコード量が増えて大変なことになります。そこで条件分岐後の処理が同じなら、それらをひとまとめにすることを考えます。

少し前にも述べていますが、1~4は全てブロック4に進むことになり、同じ処理になっています。どうやらここはまとめることができそうです。そうなると残りは5のみですから、二択の条件分岐を作ってあげれば良いことになります。それが13行目のコードになります。

if not select_vvalue1 == "その他":

この文の意味するところは、もしselect_vvalue1の値が「その他」ではなかったならば、になります。その他ではないのですから、1~4のどれか、ということになります。つまりこの一文で1~4の時の条件分岐をまとめて記述できている、ということなんですね。

それを実現しているのは論理演算子であるnotのおかげです。not否定という意味があり、YesまたはNoといった二択のみが許されているような時に、今選んでいるものの反対を返す演算子です。これは文章よりも、図で考えた方が良いでしょう。

集合A
集合A
円の中に1~4が入っています
集合B
集合B
円の外に5があります

上の図について、枠の中には、select_vvalue1が選べる値全てが入っています。このうち、1~4は処理が同じですから、まとめて円の中に入れてグループ(集合)化しています。これをまとめて集合Aと見ます(左側の図)。となると、残りのA以外の部分には5しかないことになります。

値5は一つしかありませんが、後から5さんの考えに賛同する番号が出てくるかもしれません。そうなった時のことも想定してこれも一つのグループと考え、集合Bと見なします。それを示したのが右側の図です。

そして、左と右の図をよく見比べてみます。

すると、それぞれの集合AとBは、お互いの図で色を反転したものにそれぞれ対応していませんか?つまり

  • 集合Aを反転した部分=集合B
  • 集合Bを反転した部分=集合A

ということです。上の図には二つの集合のみがあり、反転操作を施すことで今見ている集合以外の集合を指定できました。したがって、この反転操作がnot演算に当たる訳です。上を書き直しますと

  • not A=B
  • not B=A

になります。もしくは上に線(バー)を付けて表すこともあります。ですので

集合Bの否定は集合A
集合Bの否定は集合A
集合Aの否定は集合B
集合Aの否定は集合B

と表すこともできる訳ですね。

今は集合Aと集合Bという二つを考えましたが、Aだけに着目し、AとnotAの関係と考えても良い訳です。このように、表か裏か、0か1か、どちらかの値を考える二者択一の関係性のデータセットはブーリアン(Boolean)型と呼ばれ、Pythonではブール型として定義されています。

また先の集合図はベン図と呼ばれていて、論理演算を考える時に大変役立ちます。加えて論理演算の結果を表にした真理値表というのもコードを書くときに有効なツールになり得ると思います。

ちなみにnotを使わなくとも

if 1 <= select_vvalue1 <= 4:

と書けば同じ処理になります。多分こちらの方がより値が具体的になっているのでコードを理解しやすくなるとは思うのですが、もしお気に入りリストに火山を追加した場合、リストに値を追加する作業とif文を書き換える作業(値の変更)が毎回必要になります。

これは保守という観点から見ると結構面倒になるでしょう。その点において、notを使って範囲を指定していれば値の追加はともかく、if文の書き換えは不要です。何もせずとも常にその他以外の値が把握されている訳ですから。

実際にテストをしている間にもリストの中身は結構変わっていましたので、利便性を考えてここのif文にはnotを使用しています。

もう一つ32行目で

elif select_vvalue2 != "戻る":

という書き方もしています。!=は比較演算子と呼ばれるもので

x != y
# xとyは等しくない

という意味になります。select_vvalue2の値は「戻る」ではない、という条件になっていますのでこれも実質二択の関係になっており、真になる時は地域が確定しています。

さて次はif文で行っている処理内容についてですが、ここは次回の解説とさせて下さい。というのも、フロー図の赤線緑線の矢印に係る処理が複合してくるからです。

どちらかの処理を追いかけていくと見えてくると思いますが、ここは何度も処理を繰り返す無限ループ構造になっています。ブロック3は火山選択部ですので、ブロック4に行くためには火山名を確定しなければなりません。そのため、ユーザーが火山を選ぶまでプログラムは火山リストの表示を繰り返すようになっています。

ループ処理の記述にはwhile文を使っていますが、コードにもある通り二つ使用していますので、ちょっとややこしくなっています。ですので先にこちらを解説した上でif文の中身について説明をした方がより筋道が通るのかなと思っています。という訳で次回に続きます。

タイトルとURLをコピーしました
/* クリッカブル用コード */