Periodic Table (Mine) - 元素周期表
ある意味、いままでで一番苦労した問題。
前に、CheckiOのツライ点として、英語、数学、アルゴリズムを挙げたが、化学も追加すべきかもしれない。周期表と言えば「スイヘーリーベー」ぐらいしか知らない私には難しすぎた。
どんな問題?
Periodic Table
http://www.checkio.org/mission/periodic-table/
元素名から、原子番号、電子配置、軌道モデルを求めよ。
引数は、元素名を文字列で渡される。
戻り値は、[原子番号, 電子配置, 軌道モデル] の list of str で返す。
例えば、酸素の場合、原子番号は、8、
電子配置は、'[He] 2s² 2p⁴' のように希ガス型で表記する。
軌道モデルは、'2 2 211' のように表記する。
例題:
assert( checkio( 'H' ) == [ "1", u"1s¹", "1" ] ), "First Test - 1s¹" assert( checkio( 'He' ) == [ "2", u"1s²", "2" ] ), "Second Test - 1s²" assert( checkio( 'Al' ) == [ "13", u"[Ne] 3s² 3p¹", "2 2 222 2 100" ] ), "Third Test - 1s² 2s² 2p6 3s² 3p¹" assert( checkio( 'O' ) == ["8", u"[He] 2s² 2p⁴", "2 2 211"] ), "Fourth Test - 1s² 2s² 2p⁴" assert( checkio( 'Li' ) == [ "3", u"[He] 2s¹", "2 1" ] ), "Fifth Test - 1s² 2s¹"
どうやって解く?
そもそも、電子配置とか軌道とかがわからねぇ。いろいろ調べて、なんとなく理解したこと。
- 原子番号が電子(陽子?)の数である。
- 電子は原子核の周りを飛んでいて、s,p,d…の軌道がある。
- s軌道には1s,2s,3s…、p軌道には1p,2p,3p…という種類があって、それぞれ収容できる電子数が決まっている。
- 電子配置は、最も近い希ガス+αの形で表記できる。
- 希ガスとは、He,Ne,Ar,Kr,Xe,Rn,Uuo
- 例えば、Heは 「1s²」、Liは 「1s² 2s¹」だから、Liを 「[He] 2s¹」と表記する。
- どの軌道に何個の電子があるかは、ほとんどは規則的だが、たまに変則的なものもある。
この問題に関しては、まともに解くのは早々に諦めた。
すべての元素の答えを辞書(dict)で作成しておいて、checkio()関数では、辞書を参照するだけにしよう。しかし、辞書を作成するにも、そもそも答えがわからないぞ・・・。
あれこれググって、このサイトを見つけた。
Electron configurations of the elements (data page) - Wikipedia, the free encyclopedia
原子番号と電子配置は、そのまま答えが載っているし、軌道モデルも、それっぽいものが書いてある。酸素(O)なら、1s2 2s2 sp4 とあるので、指数の 2, 2, 4 から、'2 2 211'に変形させればよさそうだ。よくわからんが、4なら211、5なら221、6なら222 というルールらしい。
後は、このサイトからデータを取得して辞書を作ればいい。手作業でやるのは面倒なので、BeautifulSoupを使ってHTMLからデータを取得することにしよう。もはやCheckiOとは関係なくなってきた。
ページのソースを眺めると、
- 最初のTABLEタグがターゲット
- 2行目までは不要(スキップ)
- 3行目以後は、3行ごとに
となっていた。
from bs4 import BeautifulSoup from urllib.request import urlopen URL = "https://en.wikipedia.org/wiki/Electron_configurations_of_the_elements_(data_page)" soup = BeautifulSoup(urlopen(URL)) tr_list = soup.find('table').find_all('tr')[2:] # 最初の2行をスキップ for tr1, tr2 in zip(tr_list[::3], tr_list[1::3]): words = tr1.th.text.split() number, symbol = words[0], words[1] if int(number) <= 118: # 今回必要なのは 118 まで noble_gas_notation = ' '.join(words[4:]) # 電子配置 orbital_model = tr2.text # 軌道モデル print(number, symbol, noble_gas_notation, orbital_model)
これで必要な情報が取り出せたが、いくつか修正する必要があった
- 電子配置は '[H3] 2s2 2p4' 形式から、'[He] 2s² 2p⁴' の指数表記に変換する
- 軌道モデルは、'1s2 2s2 2p4' 形式から、'2 2 211' 形式に変換する。
また、軌道モデルでは、Wikipediaでの並び順と、答えで求められる並び順が違うようだ。
Wikipediaでは、1s,2s,2p,3s,3p,3d,4s,4p,4d,4f,5s… だが、
この問題では、1s,2s,2p,3s,3p,4s,3d,4p,5s,4d,5p… となっている。
よくわからんが、Aufbau Principleというルールらしい。
from bs4 import BeautifulSoup from urllib.request import urlopen import re SUP = ['\u2070', '\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'] ORDER = ['1s', '2s', '2p', '3s', '3p', '4s', '3d', '4p', '5s', '4d', '5p', '6s', '4f', '5d', '6p', '7s', '5f', '6d', '7p'] SIZE = {'s': 2, 'p': 6, 'd': 10, 'f': 14} def split_sup(s): # '1s2' -> ('1s', '2') m = re.match('\d+[a-z]+', s) return (m.group(), s[m.end():]) if m else (s, '') def replace_sup(s): # '1s2' -> '1s²' o, sup = split_sup(s) return o + ''.join(SUP[int(c)] for c in sup) def to_orbital_text(size, n): # (6, 4) -> '211' length = size // 2 return '1' * n + '0' * (length - n) if n < length else '2' * (n - length) + '1' * (size - n) def get_orbital_model(tokens): # '1s2 2s2 2p4' -> '2 2 211' orbitals = sorted([split_sup(t) for t in tokens], key=lambda p: ORDER.index(p[0])) return ' '.join(to_orbital_text(SIZE[o[-1]], int(n)) for o, n in orbitals) URL = "https://en.wikipedia.org/wiki/Electron_configurations_of_the_elements_(data_page)" soup = BeautifulSoup(urlopen(URL)) tr_list = soup.find('table').find_all('tr')[2:] for tr1, tr2 in zip(tr_list[::3], tr_list[1::3]): words = tr1.th.text.split() number, symbol = words[0], words[1] if int(number) <= 118: noble_gas_notation = ' '.join(replace_sup(t) for t in words[4:]) orbital_model = get_orbital_model(tr2.text.split()) print(' "{}": ["{}", "{}", "{}"],'.format(symbol, number, noble_gas_notation, orbital_model))
実行すると、
'H': ['1', '1s¹', '1'], 'He': ['2', '1s²', '2'], 'Li': ['3', '[He] 2s¹', '2 1'], (中略) 'Lv': ['116', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁴', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 211'], 'Uus': ['117', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁵', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 221'], 'Uuo': ['118', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁶', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 222'],
と表示されるので、前後に少し加えれば、解答ができあがる。
dic = { 'H': ['1', '1s¹', '1'], 'He': ['2', '1s²', '2'], 'Li': ['3', '[He] 2s¹', '2 1'], (中略) 'Lv': ['116', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁴', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 211'], 'Uus': ['117', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁵', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 221'], 'Uuo': ['118', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁶', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 222'], } checkio = dic.get
これをCheckiOで実行すると、無事にクリアできた。しかし、公開するのはちょっとなぁ。
でも、BeautifulSoupで取得する部分まで含めて公開すれば面白いかもしれない。というわけで、公開用にまとめてみた。
まとめ
try: from bs4 import BeautifulSoup from urllib.request import urlopen import re SUP = ['\u2070', '\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079'] ORDER = ['1s', '2s', '2p', '3s', '3p', '4s', '3d', '4p', '5s', '4d', '5p', '6s', '4f', '5d', '6p', '7s', '5f', '6d', '7p'] SIZE = {'s': 2, 'p': 6, 'd': 10, 'f': 14} def split_sup(s): # '1s2' -> ('1s', '2') m = re.match('\d+[a-z]+', s) return (m.group(), s[m.end():]) if m else (s, '') def replace_sup(s): # '1s2' -> '1s²' o, sup = split_sup(s) return o + ''.join(SUP[int(c)] for c in sup) def to_orbital_text(size, n): # (6, 4) -> '211' length = size // 2 return '1' * n + '0' * (length - n) if n < length else '2' * (n - length) + '1' * (size - n) def get_orbital_model(tokens): # '1s2 2s2 2p4' -> '2 2 211' orbitals = sorted([split_sup(t) for t in tokens], key=lambda p: ORDER.index(p[0])) return ' '.join(to_orbital_text(SIZE[o[-1]], int(n)) for o, n in orbitals) # parse HTML table with BeautifulSoup URL = "https://en.wikipedia.org/wiki/Electron_configurations_of_the_elements_(data_page)" soup = BeautifulSoup(urlopen(URL)) tr_list = soup.find('table').find_all('tr')[2:] dic = {} for tr1, tr2 in zip(tr_list[::3], tr_list[1::3]): words = tr1.th.text.split() number, symbol = words[0], words[1] if int(number) <= 118: noble_gas_notation = ' '.join(replace_sup(t) for t in words[4:]) orbital_model = get_orbital_model(tr2.text.split()) dic[symbol] = [number, noble_gas_notation, orbital_model] except ImportError: dic = { 'H': ['1', '1s¹', '1'], 'He': ['2', '1s²', '2'], 'Li': ['3', '[He] 2s¹', '2 1'], (中略) 'Lv': ['116', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁴', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 211'], 'Uus': ['117', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁵', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 221'], 'Uuo': ['118', '[Rn] 5f¹⁴ 6d¹⁰ 7s² 7p⁶', '2 2 222 2 222 2 22222 222 2 22222 222 2 2222222 22222 222 2 2222222 22222 222'], } checkio = dic.get
http://www.checkio.org/mission/periodic-table/publications/natsuki/python-3/beautifulsoup/
BeautifulSoupに対応していれば Wikipedia からdicを作成するし、対応してなければ ImportErrro が発生するので、あらかじめ用意したdicを使用する。(もちろん、CheckiOはBeautifulSoupに対応していない。)
出題者の求める答えとは全然違うだろうけど、わりと面白い答えができたかな?