【Python3】言語処理100本ノック 第一章
6月から外部研修が終わり、社内でOJTとやらが始まりました。
これからは自然言語処理を教えて頂けるらしく、かの有名な「自然言語処理100本ノック2015」を解いていくのが目下のお仕事となっています。
もうそこそこ進めてしまっているのですが、とりあえずアウトプットも兼ねて1章から発表していきます。
方針としては、
・Pythonらしいコードで
・ハイパフォーマンスで
・おしゃれなコード
を目標としてやっていきます。
『Pythonクックブック(第二版)』によると、Pythonという言語はPythonらしく書けばおのずとハイパフォーマンスになるようになっているらしいので、ひとつ達成できれば自然と全部満たすコードになるんじゃないかな。おしゃれかは知らんけど。
ここもっとパフォーマンスあげられるよ!という部分ありましたらご指摘してもらえるとありがたいです。順次更新していきます。
では頭から順番に解いていきます。
解説はあっさりめでいきます。
第一章
00. 文字列の逆順
文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.string = "stressed" ans = string[::-1]
Pythonの魅力のひとつ、スライス処理ですね。
02. 「パトカー」+「タクシー」=「パタトクカシーー」
「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.str1 = "パトカー" str2 = "タクシー" ans = "".join([i+j for i, j in zip(str1, str2)])
zip関数がでてきました。
処理部分は、リスト内包表記を使って一行で回答しています。
いけるところまでワンライナーでいくぞー。
03. 円周率
"Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.words = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." words = words.replace(".", "") words = words.replace(",", "").split() ans = [len(i) for i in words]
即堕ち2コマ。(ワンライナー失敗)
replaceって一度に複数の値を置換できないんですよね。
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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.words = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can." words = words.replace(".", "").split() one = [1, 5, 6, 7, 8, 9, 15, 16, 19] ans = {(index, element[:1 if index in one else 2]) for index, element in enumerate(words, start=1)}
enumerate関数はこういうときに便利です。startで開始番号を指定できるのがポイントです。
ただ、このコードやっやこしい!
内包表記はスッキリしますし速度も速いですが、処理が増えたときの可読性はどうなんだろう。
慣れれば読みやすくなるかな?
05. n-gram
与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,"I am an NLPer"という文から単語bi-gram,文字bi-gramを得よ.def ngram(seq, n): return [(seq[i:i+n])for i, j in enumerate(seq)if len(seq[i:i+n]) == n] words = "I am an NLPer" list_ = words.split(" ") ans_word = ngram(words, 2) ans_list = ngram(list_, 2)
そもそもn-gramが何かすら知らなかったのでいろいろと苦労した問題。
個人的に if len(seq[i:i+n])==n の部分が場当たり的な感じがして気に入らないですね。なんとかならんだろうか
06. 集合
"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,'se'というbi-gramがXおよびYに含まれるかどうかを調べよ.def ngram(seq, n): return {(seq[i:i+n])for i, j in enumerate(seq)if len(seq[i:i+n]) == n} X = ngram("paraparaparadise", 2) Y = ngram("paragraph", 2) # 和集合 union = X&Y # 積集合 intersection = X|Y # 差集合 difference = X.difference(Y) # "se"がXおよびYに含まれるかどうか bigram_in = "se" in X and "se" in Y
集合演算は始めてやったのでカンニングしました。(白状)
ポイントは、集合演算子はset型しか受け付けないので、ngram関数の戻り値をセット内包表記に変えている点です。
「AおよびBに含まれるかどうか調べよ」という文言は複数の解釈ができそうですが、私は「Aに含まれる且つBに含まれるかどうか調べよ」と考えました。
07. テンプレートによる文生成
引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y="気温", z=22.4として,実行結果を確認せよ.def temp(x, y, z): return str(x)+"時の"+str(y)+"は"+str(z) x = 12 y = "気温" z = 22.4 print(temp(x, y, z))
特に言うことはないですね。
文字列を接続するときはstr型に変換するのを忘れないでねってことくらいか。
08. 暗号文
与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.・英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.
def cipher(string): return ''.join(chr(219-ord(i)) if 'a'<=i and i<='z' else i for i in string) string = "My power is 53,0000." ciphered = cipher(string) original = cipher(ciphered)
a~zの文字列のみord関数でUnicode番号に変換して、chr(219-文字コード)で暗号化。
面白いのは、暗号化された文字をふたたびchr(219-文字コード)すれば元の文字にもどることですね。
09. Typoglycemia
スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ.import random words = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ." words = words.replace(".", "") words = words.replace(":", "").split() def shuffle_body(word): body = list(word[1:-1]) random.shuffle(body) return (word[0]+"".join([i for i in body])+word[-1]) ans = [shuffle_body(i)if len(i)> 4 else i for i in words]
うーん、素直すぎただろうか。
スマートな書き方があればぜひそうしたかったが、いくら考えても思いつかなかったので断念。
以上、第一章の9問を解いてみました。
一章に限らず、「知っていれば簡単だけど知らないと難しい」という問題が多いので、Pythonの便利な処理を知る上で100本ノックはとてもいい教材だと思います。
腕試しもかねて、皆さんも解いてみてはいかがでしょうか。
そして私よりもキレイで軽いコードが書けたらぜひ教えてください!!
【Python3】競技プログラミングの回答方法がわからないときに見るもの
研修でJavaばっかりやってるので全然Pythonの勉強ができない。
いや時間はあるんですが、いかんせん体力がね...
ジム通いでも始めたいところですが、今度はジムの会費が高すぎて払えないという。貧乏社会人のつらいところです。
さて、今回はPython初心者の方向けの記事を書きます。
Pythonの基本的な文法は入門書や学習サイトで勉強したし、いっちょ競技プログラミングで腕試しするか!という方が、陥りがちな落とし穴があります。
paizaとかCodeIQとか、そのへんは難易度的にもとっつきやすいんですが、この落とし穴を乗り越えない限り、解法は分かるのになぜか不正解になる...ということになってしまいます。
・標準入力
よくあるつまづきポイントがこちら。
Nが与えられ、それをうんたらかんたらした結果を出力して下さい。 なお、Nは標準入力から渡されます。
標準入力?
なにいってんだこいつ。
この言葉を初めて見た方はこう思うことでしょう。
私もこれが分からず不正解を出し続け、ついに「与えられた整数をすべて足し合わせた数を出力してください」なんていうクッソ簡単な問題で時間切れになってしまい、ショックでその後3日間パソコンに触らなかった悲しい過去があります。
標準入力を受け取る
これは要は「キー入力を受け付ける」操作をして値を受け取ってね、ということなのです。
input()関数を使いましょう。
>>> n = input()
これで変数nに入力された値、さっきの問題でいうところのNが代入されます。簡単だな!
なお、input()で得られた値は問答無用でstr型になるので、計算問題の場合は型変換しましょう。
>>> # 1を入力 >>> n = input() >>> print(n, type(n)) 1 <class 'str'>
>>> # 1を入力 >>> n = int(input()) >>> print(n, type(n)) 1 <class 'int'>
スペース区切りの標準入力
変則パターンとして、「スペースで区切られたN、Mが与えられる」というものもあります。
input()は与えられた入力を一行まるまる取得するので、行の中で要素を区切りたいときはinputに加えひと手間必要になります。
split()関数を使います。
>>> # 「1 2」を入力 >>> n = input().split(" ") >>> print(n) ['1', '2']
これでsplit()で指定された文字列で区切られた値を、配列に格納された形で得ることができました。
「指定された文字列」なので、例えばカンマで区切られているときはinput(",")とすることもできます。
配列の型変換
ここで注意したいのが、int()型変換は配列に対して使うことができないということです。
さっきと同じノリで型変換をしようとするとエラーを出します。
>>> # 「1 2」を入力 >>> n = int(input().split(" ")) int() argument must be a string, a bytes-like object or a number, not 'list'
>>> # 「1 2」を入力 >>> n = int(input()).split(" ") invalid literal for int() with base 10: '1 2'
※ 配列にする前に型変換をしようとしても、文字列(スペース)を含む値をintに変換することはできません。
配列の要素を型変換したい場合は、map()関数を使いましょう。
>>> # 「1 2」を入力 >>> n = input().split(" ") >>> n_int = list(map(int,n)) >>> print(n_int) [1, 2]
mapとlistという見慣れない関数が出てきましたが、一つ一つ解説していきます。まずmap()関数から。
map関数は、第一引数(intの部分)に任意の関数を、第二引数(nの部分)以降に任意の配列を指定することで、「配列の要素全てに、指定した関数の処理を加える」というものです。
応用範囲が広そうでゾクゾクしませんか?
つまり、前述のコードは、このコードと同じことをしていることになります。
>>> # 「1 2」を入力 >>> n = input().split(" ") >>> for i in range(len(n)): ... n[i] = int(n[i]) >>> print(n)
先ほど、配列全体に対してint()をかけることができないことを確認しました。
ならば、配列の要素ひとつひとつはただのstr型なので、それらひとつひとつにint()をかけてやればいいじゃん、ということです。
次にlist()関数ですが、これは引数を配列型に変換する関数です。int()やstr()の仲間ですね。
なぜこの関数が必要かというと、map()関数は引数に配列を要求しておいて戻り値にはイテレータを返す、というとんだアナーキストだからです。
戻り値として返されたイテレータを、もう一度配列型に変換する必要があるんですね。
>>> # 「1 2」を入力 >>> n = input().split(" ") >>> n_iter = map(int,n) >>> n_int = list(map(int,n)) >>> print(n_iter) >>> print(n_int) <map object at 0x7fb604c48518> [1, 2]
map()関数は便利なので覚えておくといいでしょう。
・標準出力
さて、無事標準入力を受け取り、アルゴリズムを生み出し、みごと配列型に格納された回答を導き出すことができました。あとはprint()関数を使い回答を出力するだけです。
問題を見ると、「スペースで区切って一行で出力してください」とあります。
改行をなくすならend = "" を使えばいいな...せや!
>>> results = [1, 2, 3] >>> for i in results: ... print(i, end = " ") 1 2 3
これだと不正解になります。なんでだよ!
わかりやすいように、値を出力した後に"|"を入れてみましょう。
>>> results = [1, 2, 3] >>> for i in results: ... print(i,end = " ") ... print("|",end = "") 1 |2 |3 |
うまく伝われば幸いですが、この回答だと3の後にスペースが入っていますね。
問題の指定は「スペースで区切る」なので、区切る必要のない最後の要素の後についているスペースは不要なスペースと判定されます。
これこそが、陥りがちな落とし穴、「余分なスペース」です。
計算結果がint型やstr型だけ、という問題なら大丈夫ですが、計算が複雑になってくると配列を使う場面がでてきます。
そんなときにfor文をまわして要素を出力すると、このように余分なスペースも一緒についてきてしまいます。
余分なスペースを出力しない
じゃあどうするかというと、join()関数を使いましょう。
>>> results = [1, 2, 3] >>> results_int = map(str, results) >>> answer = " ".join(results_int) >>> print(answer) 1 2 3
join()関数は、指定した文字列を、指定した配列の要素の間に割り込ませて一つの文字列にする、という処理をします。
「要素の間」なので、間の無い最後の要素にスペースが入る心配はありませんね。
join()はstr型の配列にしか使えませんので、map()関数を使いresults配列の要素をstr型に変えてから使います。
これで余分なスペースの無い、すっきりした文字列を出力することができました。
map()やjoin()を知らないと、初期の私のようにこんなクソコードを書く羽目になってしまうので、知らなかった方はぜひ覚えていってください!
>>> results = [1, 2, 3] >>> for i in results: ... print(i,end = "") ... if i != results[len(results)-1]: ... print(" ",end = "") 1 2 3
入力と出力をマスターして、良い競技プログラミングライフを!
【Python3】Decimalモジュールを使った、精度の高い小数点以下の数値計算
そろそろ雑談ばかりじゃなくて技術的なことを書いていこうと思った。
通常、小数を表現するときはfloat型を使うと思います。
普段はそれでもいいのですが、金融や会計の場面でこのデータ型を使ってはならない、という注意書きを見たことがある方もいるかもしれません。私はJavaの教科書で見ました。
floatやdoubleを使って計算をすると、微妙~~~な誤差が入り込んでしまうため、完璧に正確な計算が求められる状況ではこのデータ型は適さないのです。
float(0.1) + float(0.2) >> 0.30000000000000004
こんな感じに。なんでこうなるかは詳しくは知りませんが、コンピュータは数値を二進数で認識してるのが関係しているようです。
じゃあどうすればいいかというと、Pythonには標準ライブラリにDecimalという十進数の計算をするモジュールが用意されています。これを使いましょう。
これは、float型のような浮動小数点形式でありながら、10進数演算も可能なデータ型であるDecimal型を使えるようにするモジュールです。
decimal.Decimal型は、整数、文字列、タプルから作ることができます。float型を使うときは、一度文字列に変換してから作りましょう。
# decimalモジュールをインポート from decimal import * # decimal.Decimal型を用意 d1 = Decimal("0.1") # float型の変数aを用意 a = float(0.2) # aをstr型に変換 b = str(a) # int型をDecimal型に変換 d2 = Decimal(b) print(d1 + d2) >> 0.3
誤差のないきれいな小数を出力することができました。float型をstr型に変換する作業を忘れるとやっぱり誤差込みの数値が出るので気をつけましょう。
ただし、Decimal型を使った計算はfloat型に比べて圧倒的に処理が遅いので、このような誤差を許さない計算ではない限り、float型を使うことをお勧めします。
Pythonで会計や金融ソフトを作る予定がある方は是非参考にどうぞ。
間違ったことを伝えると大変なので、突っ込みどころを見つけたガチ勢の方はご教授願います。
それでは!
外部研修のはじまり
おはようございます。
初出勤の月曜日から金曜日までが終わり、社会人としての初めての一週間が終わりました。
一日中勉強しっぱなしというのはなかなか大変で、家に帰ってからは家事もそこそこにすぐ寝てしまう日々でした。自主学習もやっていきたい気持ちはあるんですがね。
ただおかげで機械学習のことがすこしづつ分かってきました。分類問題と回帰問題のやり方を一通り試してみて、基本的な考え方が理解できました。あとはいろいろなアルゴリズムを勉強して、このケースだとこの手法が最善!みたいな判断を下せるようになっていきたいです。
DeepLearningはちょっと...数式が難しすぎてですね...ニューラルネットはわかるんですが。
さて、きのうの金曜日から、それまでの社内研修(といっても各自Pythonの勉強をやるだけ)が終わり、他の会社でのJava研修が始まりました。
二カ月Javaの研修をやって、6月からはもう一度社内に戻ってPythonを使ったプロジェクト(たぶん人工知能関連?)に参加して、結果如何ではそのまま研究部署に配属になるそうです。
なので正直なところJavaよりもPythonと機械学習の勉強を詰めたいところですが、Javaはエンジニアにおける最低限の教養みたいなところがあるらしいのでやむなしですね。
私自身Javaは初めてではなかったので初日はつまづくことはなかったですが、それこそ基本文法(printlnとか条件分岐、ループ文)しか知らないのでこれから先大変になってきそうです。
久しぶりにJavaを触った感触としては、普段から使って指に染みついてるPythonとの違いになかなか慣れませんね。
行末にコロンをつけるのめんどくさすぎです。100回は間違えました。
インデントをうまく使ってコードを読みやすく!と言われても、ブロックをインデントで表現するPythonを普段から使ってれば、いつものノリで記述すれば自然とできてますしね。そういう意味ではPythonはプログラミングの入門言語として優秀、という言説もうなずけます。
あ、でもJavaの自動型変換は便利だなあと思いましたね。
int a = 123; String b = "abc";
System..out.print(a+b)
>> 123abc
みたいなの。
Pythonでこれやるとエラー出ちゃいますから。
a = 123
b = "abc"
print( a + b)
>> unsupported operand type(s) for +: 'int' and 'str'
言語は一長一短、そういうあたりまえなことが再確認できた一日でした。
〈雑記〉
あとプログラムとは関係ないんですが、昨日夜8時ごろにNHKの集金人?の人が来まして。
引っ越ししたならNHKに連絡くれないと困るじゃないですか、音沙汰無いのでこっちからきてやりましたよ的な感じでした。そんな連絡しないといけないの初耳だったのでびっくりしました。
それで、契約してくださいねみたいなことを言われましたが、うちTVおいて無いんですよね。これマジ。番組見る暇あったら勉強しないと文系出身エンジニアはやってけないんだよなぁ!?スマホもiphoneです。
その旨をつたえたら端末?にTVを持ってないことを記録するのでこの紙に名前と住所と電話番号を書いてね、と言われたので書きました。
でもそれ明らかに契約書なんですよね。ほんとにこれに書いて大丈夫なんですかって聞いても、ハンコと口座番号かいてないから契約にはならん、あくまでTV未所持を登録するための処理ですよ、と言うので「はあそうですか」、と。
これ私やらかしましたかね?このやりとり録音してませんし。
契約ってのは極端な話口約束だけで成立するので、ハンコ押してなくても一応契約書は完成してるので、後で「あなた契約してるので受信料払ってね」ってなりかねないんですよね。勘弁してくれ。
TVないのはほんとなので、最悪そうなったら契約無効か取消にしてもらおう、そう思って日々震えて過ごす新社会人でした。社会って怖い。
初出勤
記念すべき初出勤の日でした。
今日は初日ということで、キャリアコンサルタントを受けたり、同期の人たちにPythonの文法を教えたりして過ごしました。
特に仕事らしい仕事もなく、みんなでPythonのお勉強をするだけで終わった一日でした。金曜日からJavaの講習が始まるそうで、それまでは今日のようにPythonの勉強をして過ごすことになりそうです。
私はPythonの経験者(始めてまともに勉強したのがPythonでした)だったのでそこそこ余裕がありましたが、それでも初心者であることには変わりないのでつっかえつっかえ周りに教えながら復習していました。
どうせ教えるならもうPython博士として周りから崇められる程度にはなりたいので、退社後に本屋に寄って『Pythonクックブック』(オライリー社)を買いました。
前々から欲しかった本ではあったのですが、なかなかのお値段(4200円+税)なので買い渋っていましたが、いい機会なので思い切って買ってしまいました。書籍購入で補助金が出る会社だったらよかったのになあ。
向こう3日間は一日中Pyhonの勉強をし続けるいわば修業期間なので、この本を気のすむまで写経して脳と指にPythonを覚えこませようと思います。
自己紹介と抱負
はじめまして、今日からブログを書いていきます。
私はこの間まで関西の大学で法律学を勉強していましたが、今春から東京で人工知能を研究しているITベンチャー企業に就職することになりました。
いわゆる文系出身エンジニアというやつでしょうか。
大学4年生まではパソコンはネットサーフィンとOfficeソフトを使うためだけの機械でした。
しかし就職活動中になんか私営業とか向いてないなー、と悩んでいたところ、理系の友人に「お前将棋とかポーカーとか得意だしプログラマ向いてるんじゃね?(適当) 最近は文系出身も多いみたいだし」みたいな無責任なことを言われ、何社か説明会に行ったりプログラミングの学習サイトで勉強したりした結果見事にどハマりし、IT系を志しました。
その後、普通のシステム開発の企業に入ろうかとも思ったんですが、ちょうどその時期はディープラーニングだの強化学習だのがホットになっていたり、大学で人工知能の法整備関連の研究をちょこっとやった経験もあったりしたので、どうせ新しく始めるなら新しい技術の人工知能関連を勉強したい!と思い、人工知能を研究しているベンチャー企業に入社することにしました。
いきなり人工知能に携われるとは思っていませんので、最初は修行の日々が続くことでしょう。
そして明日が入社式ということで、期待3割、不安7割くらいのマインドでいままで過ごしてきました。
機械学習と数学とプログラミング言語の書籍を買い込み、いままでちょこちょこと勉強してきました。基本的な記述は書けるようになりましたので、一念発起してブログを立ち上げ、githubとTopcoderに登録した次第です。
このブログでは、仕事や趣味の中で触れた技術的な発見とかを書いていきたいと考えています。あと雑記もかけたらいいなと思ってます。
最初はとても基本的なことばっかり書いていくことになりますが、そのうち機械学習とか、ハイレベルなことも扱っていこうと思ってますので、どうかよろしくお願いします。