乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

プログラミング

今回は
自然言語処理100本ノックの準備運動を解いてみました。



解説動画はこちら


自然言語処理とは
コンピューターに言語を処理させるための
一連の方法です

自然言語処理100本ノックはこちら
自然言語処理100本ノック

プログラミングを行う上で
こういった文字列の処理方法を覚えておくと
プログラミングスキルが格段にアップします。

やっておいて損はしないと思います。

どの言語でも対応することは可能です。

さてPython言語を用いて
1つ1つ解いていきましょう。

第1章: 準備運動 からです。

00. 文字列の逆順

文字列"stressed"の文字を逆に
(末尾から先頭に向かって)並べた文字列を得よ.

解き方としては
文字列のリバース方法を考えるです。

Python言語の
リスト型などはリバース用の関数がありますが
文字列には無いようです。

Pythonでは次のようにすることで実現できます。
"stressed"[::-1]
'desserts'

これだけです。
インデックスを用いると文字列を切り出すことができます。

インデックスの書き方は
開始位置 : 終了位置 : 飛ばす個数
になっています。

-1することで終わりから抽出を行います。

01. 「パタトクカシーー」
「パタトクカシーー」という文字列の
1,3,5,7文字目を取り出して連結した文字列を得よ.

文字の奇数番目を取り出す方法です。

Pythonの文字列のインデックスの操作で簡単に取り出せます。
"パタトクカシーー"[0::2]
'パトカー'

これだけですね。
インデックスの最後を2とすることで
1こ飛ばしで文字を抽出できます。

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を
先頭から交互に連結して文字列「パタトクカシーー」を得よ.

文字を交互に組み上げる問題です。

ここから少し考えるのが必要になりそうですね。
こういうのは業務でも良く出てきそうな問題なので
覚えておくと仕事がはかどりますね。

文字列からの文字の切り出しは
インデックスを用いて
行うことができました。

単純の文字と文字を足すと
文字の連結になってしまいます。

交互にというのがポイントです。

zip関数を使うと2つの文字列やリストなどを組み合わせて
使うことができます。

''.join([p+t for p,t in zip('パトカー','タクシー')])
'パタトクカシーー'

一旦zipで1文字ずつ交互に切り出し、
それを内包表記で書いてあげるとリスト型になります。

文字列型のjoin関数でリスト型を文字列に連結できます。

03. 円周率
"Now I need a drink, alcoholic of course,
after the heavy lectures involving quantum mechanics."
という文を単語に分解し,
各単語の(アルファベットの)文字数を
先頭から出現順に並べたリストを作成せよ.


最終的に欲しいものはリストです。

各単語に分解するというのは
文字列型のsplit関数でスペース区切りで区切ると
実現できます。

アルファベットの・・・
と書かれているので
それ以外はの文字が不要になりますね。

文字列の削除はreplace関数で実現できます。

文章を単語にしたら
欲しいのは文字数ですね。

文字列の文字数はlen関数で測ることができます。

出来上がったコードはこのような形になりました。

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

[len(s) for s in w.replace(',','').replace('.','').split(' ')]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

replaceを二回続けていますが
reライブラリを用いると簡素になります。

import re

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

w = re.sub(r'[.,]', "", w)

[len(s) for s in w.split(' ')]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]


04. 元素記号
"Hi He Lied Because Boron Could Not Oxidize Fluorine.
New Nations Might Also Sign Peace Security Clause.
Arthur King Can."
という文を単語に分解し,
1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,
それ以外の単語は先頭に2文字を取り出し,
取り出した文字列から単語の位置(先頭から何番目の単語か)
への 連想配列(辞書型もしくはマップ型)を作成せよ.


最終的に欲しいものは辞書型ですね。

まず単語分解は前問と同じようにsplitで行います。

次に考えることは何文字切り出すです。
これも文字列のインデックスで
実現できます。

文字の切り出しに条件がついています。
1,5,6,7,8,9,15,16,19番目だけ1文字
他は2文字

ややこしい条件ですよね。
条件分岐は IF文を用います。

文章から単語に切り分けた段階でリスト型になっています。
リスト型では順番に処理することができるので
そこにenumerate関数を加えると
何番目という数値を取ることができます。

あとは条件分岐とうまく設定すればいいです。

コードはこうなりました。

w = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

{i+1:s[0] if i+1 in [1,5,6,7,8,9,15,16,19] else s[0:2] for i,s in enumerate(w.split(' '))}

内包表記では
FOR , IF文を組み合わせて使うことができます。

05. n-gram
与えられたシーケンス(文字列やリストなど)から
n-gramを作る関数を作成せよ.

この関数を用い,
"I am an NLPer"という文から
単語bi-gram,文字bi-gramを得よ.

n-gram
任意の文字列や文書を連続したn個の文字や単語で
分割するテキスト分割方法.

特に,nが1の場合をユニグラム(uni-gram)
2の場合をバイグラム(bi-gram)
3の場合をトライグラム(tri-gram)と呼ぶ.

ということでバイグラムを取得するための
関数を作れという問題ですね。

文字列はそのままfor文の繰り返しに用いることができます。

そこからインデックスを用いて
n文字を取得していけば良いという考えです。

コードは非常にシンプルになります。

def n_gram(st, n):
    return [st[i:i+n] for i in range(len(st)-n+1)]

for i in range(1,4):
    print(n_gram("I am an NLPer",i))
['I', ' ', 'a', 'm', ' ', 'a', 'n', ' ', 'N', 'L', 'P', 'e', 'r']
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
['I a', ' am', 'am ', 'm a', ' an', 'an ', 'n N', ' NL', 'NLP', 'LPe', 'Per']

単語単位でやりたい場合は
一旦スペースでsplitすれば
単語単位のn-gramになります。

for i in range(1,4):
    print(n_gram("I am an NLPer".split(' '),i))
[['I'], ['am'], ['an'], ['NLPer']]
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
[['I', 'am', 'an'], ['am', 'an', 'NLPer']]

まだまだ準備運動は続きますね

今回はここまでです
それでは。



 

Youtubeの動画をダウンロードしたいと
思ったことないでしょうか?


Pythonプログラムなら
・・・

出来てしまうんですねーーー


解説動画はこちら



さてやり方についてですが
まずはプログラムをインストールしましょう。

youtube-dl

Youtubeの動画をダウンロードするプログラムです。

pipなどでインストールしておきましょう。

sudo -H pip install --upgrade youtube-dl

インストールできたら
まずはライブラリのインストールです。

import youtube_dl

これだけ

動画のダウンロードに関しては
まず動画のURLを調べておきます。

例:
https://www.youtube.com/watch?v=XXXXXXXXXXX

このXXXの部分が動画のIDになるので
これを控えておきます。

otupyの過去の動画のリンクを用いて
ダウンロードするプログラムは
以下のようになります。

download_url = 'https://www.youtube.com/watch?v=6PjkBT7QwiM'
output_file_name = 'abe'

ydl_opts = {
    'format': 'bestvideo+bestaudio/best',
    'outtmpl':  output_file_name + '.%(ext)s',
}

ydl = youtube_dl.YoutubeDL(ydl_opts)
result = ydl.extract_info(download_url, download=True)

URLと保存する際のファイル名を設定すれば
プログラムを実行した場所にダウンロードされると思います。

Jupyter Notebookであればノート上での再生も可能です。
import IPython.display
IPython.display.Video('abe.webm',width=480, height=270)
スクリーンショット 2020-02-02 16.26.47



はい、結構簡単に
ダウンロードできるんじゃないでしょうか?

注意点としては
動画の拡張子は
色々あるようで、webm,mkvなどは
そのままでは見れないかもしれません。

その場合は一旦mp4などに変換すれば
見れるかと思います。

大量にダウンロードしたいニーズがあるなら
試してみてはいかがでしょうか

今回はここまでです
それでは。





さて、今年も受験シーズンですねーー

数学の問題をプログラムで解くという
無駄なことをやっていきたいと思います。

解説動画はこちら





2020年の数学1の問題です。

さてまず最初は

スクリーンショット 2020-01-26 16.48.25

問題見るだけだと
なんだかよく分からないですよねー

まずはこの直線L君を作図してあげましょう。
そうすれば分かりやすくなるはずです。

Python言語では
数学的な作図が非常に楽です。

数式用、作図用のライブラリを
インポートします。
import numpy as np
import warnings
import matplotlib.pyplot as plt
warnings.simplefilter('ignore')
%matplotlib inline

作図では直線を描くので
x軸、y軸の値が必要になります。

y軸の値は数式を元に生成されるので
x軸の値を適当に生成します。

numpyのlinspaceで
数値を適当に生成することができます。
a=1
x = np.linspace(-5, 5, 100)
y = (a**2-2*a-8)*x + a
plt.plot(x, y,label=str(a))
plt.legend()
plt.show()
download



aが1の時を作図してみると
傾きは右肩下がりで負の値になっています。

今度はaが8の時を試してみます。
download-1

そうすると右肩上がりで
傾きが正の値になりました。

どこかに境界線があるはずです。

aの値を複数用いて
まとめて作図してみます。
for a in range(1,6):
    x = np.linspace(-5, 5, 100)
    y = (a**2-2*a-8)*x + a
    plt.plot(x, y,label=str(a))

plt.legend()
plt.show()
download-2
aの値を1から5までで作図すると
4の時に平行になったように見えます。
5になると正の値になっているように見えます。

と言うことで4が境界線に
なっていると言えそうです。

ただし、aの値は負の値を取ることも
考えられますので、aが負の時も
作図してみます。
for a in range(-6,0):
    x = np.linspace(-5, 5, 100)
    y = (a**2-2*a-8)*x + a
    plt.plot(x, y,label=str(a))

plt.legend()
plt.show()
download-3
aが負の場合
-2で平行になっているように見えますね。

合わせると回答すべきポイントは
アイ< a < ウなので

-2 < a < 4

と言う感じになりますね。

ただしこれだと
図からの判断なので
実際に傾きを求めてみましょう。

傾きは
スクリーンショット 2020-01-26 16.48.46
にて求めることができます。

x,yの共分散は
np.cov(x,y)[0,1]

xの分散は
np.var(x)

これで求めることができます。

これでaを入れて傾きを求めてみると
for a in range(-5,6):
    x = np.linspace(-5, 5, 100)
    y = (a**2-2*a-8)*x + a
    
    # 傾き = x,yの共分散 / xの分散
    coef = np.cov(x,y)[0,1] / np.var(x)
    print(a,'\t',coef)
-5 	 27.27272727272728
-4 	 16.161616161616156
-3 	 7.070707070707068
-2 	 0.0
-1 	 -5.050505050505051
0 	 -8.080808080808078
1 	 -9.090909090909092
2 	 -8.080808080808078
3 	 -5.050505050505051
4 	 0.0
5 	 7.070707070707068




はい、これで-2と4のところで
ちゃんと傾きは0で平行になっていますので
それを越えれば傾きが正になることが確認できました。


続いて

スクリーンショット 2020-01-26 16.48.55

先ほどの直線Lとx軸の交点と言っているので
x軸を作図してあげれば
交点bが分かりやすくなります。

プログラム上では
単純にy=0とし、xを適当な値で結んで
直線を描けばx軸になります。
a=1
x = np.linspace(-5, 5, 100)
y = (a**2-2*a-8)*x + a

# 直線Lを作図
plt.plot(x, y,label=str(a))
# x軸を作図
plt.plot([-5,5], [0,0],c='red')
plt.legend()
plt.show()
download-4

はい、赤線と青線の交わる部分が交点bですねー

さてaが変われば傾きが変わり
交わる部分も変わってくるのですが

求めたい交点bについては
yの値は0なのでxの値を求めてあげれば
良いと言うことになります。

と言うことで
直線の式を変形して
xの値を求めてみましょう。

式を変形すると
y = (a**2-2*a-8)*x + a

y-a = (a**2-2*a-8)*x

(y-a)/(a**2-2*a-8) = x

x = (y-a)/(a**2-2*a-8)
y=0なので、yの部分を0に変えてあげれば
xの値をすぐに求めることができますね。

aを変えてxを求めてみましょう。

まずは a > 0 の場合

for a in range(0,11):
    if (a**2-2*a-8)!=0:
        x = (0-a)/(a**2-2*a-8)
        print(a,'\t',x)
0 	 -0.0
1 	 0.1111111111111111
2 	 0.25
3 	 0.6
5 	 -0.7142857142857143
6 	 -0.375
7 	 -0.25925925925925924
8 	 -0.2
9 	 -0.16363636363636364
10 	 -0.1388888888888889

これでみると4のところが無くて
5から負の値になっていますね。

4の値をもう少し細かく見てみましょう。
for a in np.linspace(3.9, 4.1, 21):
    if (a**2-2*a-8)!=0:
        x = (0-a)/(a**2-2*a-8)
        print('{:.03}'.format(a),'\t',x)
3.9 	 6.610169491525415
3.91 	 7.3510058281631725
3.92 	 8.277027027027007
3.93 	 9.467598169115819
3.94 	 11.054994388327726
3.95 	 13.277310924369653
3.96 	 16.610738255033528
3.97 	 22.166387493020434
3.98 	 33.277591973244355
3.99 	 66.6110183639381
4.01 	 -66.72212978369552
4.02 	 -33.38870431893784
4.03 	 -22.277501381979285
4.04 	 -16.72185430463576
4.05 	 -13.388429752066106
4.06 	 -11.166116611661277
4.07 	 -9.578724405742625
4.08 	 -8.388157894736848
4.09 	 -7.462141944900592
4.1 	 -6.721311475409841

と言うことで4を越えると
xが0未満になり成立しません。

a > 0 の場合、b > 0となるのは エ < a < オ
なので

答えは

0 < a < 4

ですね。


a<=0の場合は
for a in range(-10,0):
    if (a**2-2*a-8)!=0:
        x = (0-a)/(a**2-2*a-8)
        print(a,'\t',x)
-10 	 0.08928571428571429
-9 	 0.0989010989010989
-8 	 0.1111111111111111
-7 	 0.12727272727272726
-6 	 0.15
-5 	 0.18518518518518517
-4 	 0.25
-3 	 0.42857142857142855
-1 	 -0.2

-2で負の値に切り替わりますね。
-2付近を細かく見ると
 for a in np.linspace(-3, -1, 21):
    if (a**2-2*a-8)!=0:
        x = (0-a)/(a**2-2*a-8)
        print('{:.03}'.format(a),'\t',x)
-3.0 	 0.42857142857142855
-2.9 	 0.466988727858293
-2.8 	 0.5147058823529413
-2.7 	 0.5756929637526651
-2.6 	 0.6565656565656565
-2.5 	 0.7692307692307693
-2.4 	 0.9375000000000004
-2.3 	 1.2169312169312176
-2.2 	 1.774193548387094
-2.1 	 3.4426229508196755
-1.9 	 -3.220338983050848
-1.8 	 -1.551724137931033
-1.7 	 -0.9941520467836252
-1.6 	 -0.7142857142857142
-1.5 	 -0.5454545454545454
-1.4 	 -0.4320987654320987
-1.3 	 -0.3504043126684635
-1.2 	 -0.28846153846153844
-1.1 	 -0.2396514161220043
-1.0 	 -0.2

はい、これで-2を界に
負の値になることが確認できたので

a <= 0 の場合、b > 0となるのは a < カキ

a < -2

となりました。

ふう
普通に問題を解くのに比べて
3倍ほどは時間がかかるんじゃないでしょうかね

このように
無駄なコードを書くことで
プログラミングを上達させることが
できるかもしれません。

お暇であれば試してみるのもいかがでしょうか?

今回はこれまでです。
それでは

カイジファイナルゲームを観てきました。

そこで新作ゲームがあったので
プログラムで
検証していきたいと思います。

検証動画はこちら




さて
まずはゴールドジャンケンについてですね

この映画の内容では
政治家向けの接待ジャンケンと言う設定です。

ルールは
・3回のジャンケン勝負
・最低1回は必ずグーを出す必要がある(純金を握る)
・純金を手にして(グーの時)勝った場合、その純金はもらえる

3回勝負のうち
1回はグーを出すと言うのがポイントですね

映画では登場人物の
高倉という官僚が考えたというもので

カイジと対戦する機会があります。

その場合は
特別ルールが加算されていました。

カイジ戦特別ルール
・カイジが1回でも勝てば勝利。
・あいこの場合、高倉の勝ち。
・純金を握って勝つごとに1億円の金塊が貰える。

高倉は3回の勝ちを狙っているのでした。


と言うことで
早速プログラムで検証していきたいと思います。

まずジャンケンの手は
パー・グー・チョキ・
しか無いので
その重複ありの組み合わせになります。

ただし一度はグーを出さないといけないので
パーとチョキだけのケースを除くことを考えると
import itertools

# 手の内
seq = ['パー','グー','チョキ']

# 出せる手を求める

# itertools.product で直積を求める
# 1度でもグーを出す必要がある
hand = [h for h in itertools.product(seq,repeat=3) if 'グー' in h]

print(len(hand))

for h in hand:
    print(h)
19
('パー', 'パー', 'グー')
('パー', 'グー', 'パー')
('パー', 'グー', 'グー')
('パー', 'グー', 'チョキ')
('パー', 'チョキ', 'グー')
('グー', 'パー', 'パー')
('グー', 'パー', 'グー')
('グー', 'パー', 'チョキ')
('グー', 'グー', 'パー')
('グー', 'グー', 'グー')
('グー', 'グー', 'チョキ')
('グー', 'チョキ', 'パー')
('グー', 'チョキ', 'グー')
('グー', 'チョキ', 'チョキ')
('チョキ', 'パー', 'グー')
('チョキ', 'グー', 'パー')
('チョキ', 'グー', 'グー')
('チョキ', 'グー', 'チョキ')
('チョキ', 'チョキ', 'グー')

27通りのうち
19ケースの組み合わせが残ります。

次にこの19ケースのみで
総当たり戦を考えてみます。

高倉に対してカイジはあいこでも負けます。
カイジが勝てるのは勝つ組み合わせの時だけです。

19ケース同士の総当たりだと
361通りの対戦パターンがあります。
count=[]
for k,t in itertools.product(hand,hand):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    count.append(tmp)
    print(k,t,tmp)

ここから勝敗結果を求めていくと
result = {}
for c in count:
    key = str(c)
    if key in result:
        result[key]+=1
    else:
        result[key]=1

for k,v in result.items():
    print(k,v)
{'kaiji': 0, 'takakura': 3} 115
{'kaiji': 1, 'takakura': 2} 159
{'kaiji': 2, 'takakura': 1} 75
{'kaiji': 3, 'takakura': 0} 12

カイジが1回でも勝てるケースは
361分の246ケースあります。

しかし、もう一つのポイントがあります。

2手目までにグーを出していない場合は
3手目でグーを出さないといけないので
相手に負けてしまうケースが存在します。
(相手も2手目までにグーを出していなければ引き分け)

ということは最後にグーを残すことは
無いだろうと思われるので
最後にグーを残してしまうケースを考えると

hand2 = []
for h in hand:
    if h[0]!='グー' and h[1]!='グー':
        print(h)
    else:
        hand2.append(h)

('パー', 'パー', 'グー')
('パー', 'チョキ', 'グー')
('チョキ', 'パー', 'グー')
('チョキ', 'チョキ', 'グー')

4ケースあります。

これを除いて考えてみることにします。

19-4で15ケースですね。
count=[]
for k,t in itertools.product(hand2,hand2):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    count.append(tmp)
    print(k,t,tmp)

result = {}
for c in count:
    key = str(c)
    if key in result:
        result[key]+=1
    else:
        result[key]=1

for k,v in result.items():
    print(k,v)

{'kaiji': 0, 'takakura': 3} 78
{'kaiji': 1, 'takakura': 2} 99
{'kaiji': 2, 'takakura': 1} 42
{'kaiji': 3, 'takakura': 0} 6

結果としては

225ケースのうち
147回はカイジが1回でも勝てるようですね。

割合としては
65%くらいの確率で
1回は勝てることになるんじゃ
なかろうかと。

そもそも
このゲームのルールだと
カイジが有利なのかもしれませんね。

2回以上勝てたケースが
有利な手順かもしれないので
それを調べてみます。
w = {1:{'パー':0, 'グー':0,'チョキ':0},2:{'パー':0, 'グー':0,'チョキ':0},3:{'パー':0, 'グー':0,'チョキ':0}}
for k,t in itertools.product(hand2,hand2):
    tmp={'kaiji':0,'takakura':0}
    for i in [0,1,2]:
        if (k[i]=='パー' and t[i]=='グー') or (k[i]=='グー' and t[i]=='チョキ') or (k[i]=='チョキ' and t[i]=='パー'):
                tmp['kaiji']+=1
        else:
                tmp['takakura']+=1
    if "'kaiji': 2" in str(tmp) or "'kaiji': 3" in str(tmp):
        print(k,t,tmp)
        w[1][k[0]]+=1
        w[2][k[1]]+=1
        w[3][k[2]]+=1
print(w)
{1: {'パー': 15, 'グー': 27, 'チョキ': 6},
 2: {'パー': 15, 'グー': 27, 'チョキ': 6},
 3: {'パー': 16, 'グー': 16, 'チョキ': 16}}


手の結果から考慮すると

1手目は「パー」か「グー」
2手目は1手目「パー」なら「グー」, 1手目「グー」なら「パー」
3手目は「パー」か「チョキ」

というパターンで出すと
勝ちやすいのかもしれない・・・


てか
そもそも
ジャンケンに必勝法なんて
存在しないですよねーーーーーーー

カイジが
勝てたのかどうかは
カイジファイナルゲームをご覧ください


それでは

新年あけましておめでとうございます。

今年が良い年になりますように
と言うことで

昨年末に大きなニュースがありました。
あのお方と似ている人の顔を入れ替えて
遊んでみました。


解説動画はこちら



まあ詳しくは動画の方をご覧くださいませ。

出来上がったものの中から
いくつかを置いておきますねwww
gonebean1

gonebean2

カルロス・ビーンさんと
Mr ゴーンさんですね。

ってか
Mr ゴーンだと
変わってねーーーー


最後にこちらは誰と
入れ替えたでしょうか?!?!
goneyoshi


と言うことで
今年も色々
プログラミングを用いて
面白おかしい講座をやっていきたいと思います。

今年度もよろしくお願いいたします。
それでは
 

このページのトップヘ