言語処理100本ノック2020年版を解いてみた【第1章:準備運動 00〜04】

自身のQiita記事(こちら)の転載です。

東北大乾・岡崎研(現乾・鈴木研)で作成された、新人研修の一つであるプログラミング基礎勉強会の教材『言語処理100本ノック 2020年版』をPython(3.7)で解いた記事です。

独学でPythonを勉強してきたので、間違いやもっと効率の良い方法があるかもしれません。
改善点を見つけた際はご指摘いただけると幸いです。

ソースコードはGitHubにも公開しています。

小出しに5問ずつ公開していけたらと考えています。

第1章: 準備運動

テキストや文字列を扱う題材に取り組みながら,プログラミング言語のやや高度なトピックを復習します.

00.文字列の逆順

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

strings = "stressed"
reversed_strings = strings[::-1]
print(reversed_strings) 
# >> desserts

スライスを使って文字列を逆順に表示しました。
スライスの使い方は文字列やリスト、タプルなどのシーケンスオブジェクトに対して[start:stop:step]のように、開始位置startと終了位置stop、増分stepの3つの値を指定して使います。
指定されたインデックスにあてはまる値が返却されます。

インデックスはstart <= index < stopのようにstartの値は含まれますが、stopの値は含まれないところに注意が必要です。
startstop最初から最後までという場合には省略可能です。
stop部分はシーケンスオブジェクトの長さを超えた値が指定されてもエラーとはならず無視されます。

今回増分に負の値が指定されていますが、この場合逆順に取り出すことができます。
逆順になるのに伴って、startstopも逆に指定する必要があります。

負の値はstartstopにも指定することができます。
この場合は通常のインデックスによる指定同様、末尾からの位置の指定となります。

01.「パタトクカシーー」

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

strings = "パタトクカシーー"
answer = strings[::2]
print(answer)
# >> パトカー

前述したスライスの増分を2にして文字列の先頭から1字飛ばしで取得することで1,3,5,7文字目を取り出しました。

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

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

strings1 = "パトカー"
strings2 = "タクシー"

answer = "".join([i + j for i, j in zip(strings1, strings2)])
print(answer)
# >> パタトクカシーー

複数の要素をまとめて取得する組み込み関数zip()を使って解きました。
forループ中で複数のシーケンスオブジェクトの要素を同時に取得でき、ここではそれぞれijの変数に代入し、連結する内包表記となっています。

文字数が同じだったのできれいに解決できましたが、zip()を使う際は要素の個数が異なる場合に多い方は無視される点にご注意です。

内包表記では同じインデックスの文字を連結したものが入ったリストができるので、それをjoinメソッドを使って一つの文字列に連結して結果としています。

03.円周率

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

pi = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."
table = str.maketrans('', '', ',.')
words = pi.translate(table).split()

print([len(i) for i in words])
# >> [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

単語に分解して、各単語の文字数を出力ということで、単語以外の部分をまずは除去。
カンマとピリオドを除去したかったので、translateメソッドを利用しました。

translateには変換テーブルが必要で、str.maketrans()関数で作成します。
str.maketrans()関数は引数に辞書もしくは3つの文字列を指定できます。
辞書の場合はキーに置換前文字(1文字)を指定し、バリューに置換後文字(削除の場合はNone)を指定します。
3つの文字列を指定する場合は、第一引数に置換前文字列、第二引数に置換後文字列、第三引数に削除文字列(省略可)を指定します。
第一引数と第二引数の文字列の長さは一致している必要があるため、置換後文字列に2文字以上を指定することはできません。

変換テーブルを引数にtranslateメソッドを使うと文字列が置換・削除(ここではカンマとピリオドが削除)されるので、スペースを引数にsplitメソッドでスペースで区切ったリストにして単語の抽出は完了です。

あとは1単語ずつ長さを求めてリスト化する内包表記を出力して完成です。

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文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.

element_symbol = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
table = str.maketrans('', '', ',.')
words = element_symbol.translate(table).split()

element_symbol_dict = {}
single_chars = [i - 1 for i in [1, 5, 6, 7, 8, 9, 15, 16, 19]]
for index, word in enumerate(words):
    length = 1 if index in single_chars else 2
    element_symbol_dict[word[:length]] = index + 1
print(element_symbol_dict)
# {'H': 1, 'He': 2, 'Li': 3, 'Be': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'Ne': 10, 'Na': 11, 'Mi': 12, 'Al': 13, 'Si': 14, 'P': 15, 'S': 16, 'Cl': 17, 'Ar': 18, 'K': 19, 'Ca': 20}

この問題もまずは『03.円周率』同様カンマとピリオドを除去し、スペースで区切った単語のリストを取得します。

元素記号の辞書を初期化しておき、指定された先頭1文字のみ取り出す単語のインデックスのリストを作成して用意完了です。

その後、まずはenumerate()関数を用いて単語のリストから1語ずつ単語とインデックスを取得します。
これにより、取得した単語のインデックスが先頭1文字のみ取り出すインデックスに含まれているかinを用いて調べることができ、含まれていたら長さ1を、含まれていなければ長さ2を設定できます。

あとはスライスで指定された数の文字をキーとし、単語のインデックス+1で単語の位置をバリューとする要素を追加します。

まとめ

この記事では言語処理100本ノック2020年版 第1章:準備運動の問題番号00〜04までを解いてみました。

普段はスライスを使うことがあまりなかったのですが、この100本ノックでは初っ端から出てきていたので言語処理ではよく使われるものなのでしょうか…?
100本ノックを解くことで言語処理に慣れていければと思います。

実力アップしたいので、よりよい解答がありましたらぜひご指摘ください!!
よろしくお願いいたします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です