はじめに
巷で流行っている自然言語100本ノックにPythonで挑戦してみます。
解き方はいろいろあると思うので、いろいろな解き方を示したいと思います。
全ての解き方を網羅することは当然ながらできません。
「こんな解き方があるよ」や「ここ間違ってるよ」という点があれば、ぜひコメントでお教えいただければと思います。
言語処理100本ノック 2020とは
言語処理100本ノック 2020は、東北大学の乾・鈴木研究室のプログラミング基礎研勉強会で使われている教材です。
詳しくは、言語処理100本ノックについてを参照して下さい。
Qiitaの記事へのリンク
1問目 | 2問目 | 3問目 | 4問目 | 5問目 | 6問目 | 7問目 | 8問目 | 9問目 | 10問目 | |
---|---|---|---|---|---|---|---|---|---|---|
第1章 | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 |
第2章 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
第3章 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
第4章 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
第5章 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
第6章 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
第7章 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
第8章 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
第9章 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
第10章 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
01. 「パタトクカシーー」
問題
01. 「パタトクカシーー」の問題は、下記の内容です。
「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.
解答
問題を読むと、パタトクカシーー
の1,3,5,7文字目なので、答えはパトカー
です。
以下に示すどの解答でも、最後の式を評価するとパトカー
と返ってきます。
シンプル
素直に文字列の末尾に追加していく方法があります。
enumerate(s)
は、n=len(s)
として(0,s[0]),...,(n-1,s[n-1])
を走査するためのイテレータを返します。
# シンプル
s = "パタトクカシーー"
result = ""
for n,c in enumerate(s):
if n%2==0:
result += c
result #=> 'パトカー'
関数型的
関数型由来のmap
やfilter
を使って書くこともできます。
この例ではあまりメリットを感じられませんね…。
# 関数型的
s = "パタトクカシーー"
"".join(
map(
lambda x:x[1],
filter(
lambda x:x[0]%2==0,
enumerate(s)
)
)
) #=> 'パトカー'
ラムダ式で定義した関数に名前を付けたほうが若干ましでしょうか。
# 関数型的
s = "パタトクカシーー"
second_elem = lambda x:x[1]
is_first_elem_even = lambda x:x[0]%2==0
"".join(map(
second_elem,
filter(
is_first_elem_even,
enumerate(s)
)
)
) #=> 'パトカー'
名前を付けすぎるとやりすぎに感じますね。
# 関数型的
s = "パタトクカシーー"
first_elem = lambda x:x[0]
second_elem = lambda x:x[1]
is_first_elem_even = lambda x:first_elem(x)%2==0
"".join(map(
second_elem,
filter(
is_first_elem_even,
enumerate(s)
)
)
) #=> 'パトカー'
これくらいの関数は準備されていてほしいですね。
なお、ラムダ式による関数定義は、下記の通常の関数定義と一緒です。
def first_elem(x):
return x[0]
def second_elem(x):
return x[1]
def is_first_elem_even(x):
return first_elem(x)%2==0
おとなしく変数に入れたほうがいいかもしれません。
filter
やmap
が返してくるのはイテレータ(ジェネレータ)なので、変数に入れるたびにリストが生成されたりはしません。
JavaScriptみたいに、コールバックが後ろ側のほうがよかった気がしてなりません。
# 関数型的
s = "パタトクカシーー"
first_elem = lambda x:x[0]
second_elem = lambda x:x[1]
is_first_elem_even = lambda x:first_elem(x)%2==0
filtered = filter(is_first_elem_even, enumerate(s))
mapped = map(second_elem, filtered)
"".join(mapped) #=> 'パトカー'
内包表記
map
やfilter
にこだわらずに、内包表記で書いたほうが簡単です。
[x for x in l]
と書くとリスト内包表記ですし、(x for x in l)
と書くとジェネレータ内包表記です。
f((x for x in l))
は、f(x for x in l)
とかっこを一つ減らして書くことができます。
複数の変数に値を同時に代入すれば、x
で受け取ってx[0]
やx[1]
で取り出すよりもわかりやすくなります。
# 内包表記
s = "パタトクカシーー"
"".join(c for n,c in enumerate(s) if n%2==0) #=> 'パトカー'
Pythonic
Python的であることを Pythonic といいますが、このコードが一番Pythonicでしょうか。
リストや文字列に対して、s[start:end:step]
でアクセスする方法をスライスと呼びます。
step
に2
を指定すると1こ飛ばしになります。
# Pythonic
s = "パタトクカシーー"
s[::2] #=> 'パトカー'
その他の解答
インデックスで回す
enumerate
なんて使わずに、自分でインデックスを用意して回す方法もあります。
# 自力で回す
s = "パタトクカシーー"
result = ""
n = 0
for c in s:
if n%2==0:
result += c
n += 1
result #=> 'パトカー'
リストに追加して後で結合する
言語処理100本ノック 2020を解く(01. 文字列の逆順)と同じようにリストに追加して後で結合することもできます。
# シンプル
s = "パタトクカシーー"
l = []
for n, c in enumerate(s):
if n%2==0:
l.append(c)
"".join(l) #=> 'パトカー'
振り返り
「文字列を結合する」のと「リストに追加する」のとどっちがいいか悩むことがあります。
文字ではなく一般のオブジェクトのリスト扱うなら必然的にリストに追加することになりますが、今回のようにゴールが文字列の生成ならどちらでもいいんじゃないでしょうか。
サイズが大きくなって遅くなってきたら立ち止まって考えましょう。
# シンプル
s = "パタトクカシーー"
result = ""
for n,c in enumerate(s):
if n%2==0:
result += c
result #=> 'パトカー'
# シンプル
s = "パタトクカシーー"
l = []
for n, c in enumerate(s):
if n%2==0:
l.append(c)
"".join(l) #=> 'パトカー'