はじめに
巷で流行っている自然言語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 |
00. 文字列の逆順
問題
00. 文字列の逆順の問題は、下記の内容です。
文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.
解答
問題を読むと、stressedを逆に並べた文字列を得る問題なので、答えはdessertsです。
以下に示すどの解答でも、最後の式を評価するとdessertsと返ってきます。
シンプル
素直に文字列の先頭に追加していく方法があります。
Pythonでは、+で文字列を結合して新たな文字列を生成できるのでした。
# シンプル
s = "stressed"
result = ""
for c in s:
result = c + result
result #=> 'desserts'
関数型的
関数型的というにはシンプルすぎますが、関数の組み合わせで表現できます。
reversed関数は、イテレータを与えると逆順に取り出すイテレータを返すので、空文字で連結して所望の結果を得ます。
# 関数型的
s = "stressed"
"".join(reversed(s)) #=> 'desserts'
Pythonic
Python的であることを Pythonic といいますが、このコードが一番Pythonicでしょうか。
リストや文字列に対して、s[start:end:step]でアクセスする方法をスライスと呼びます。
stepに-1を指定すると逆順になります。-1ずつ進むんですね。
# Pythonic
s = "stressed"
s[::-1] #=> 'desserts'
その他の解答
組み合わせ
上記を組み合わせた、下記のような方法もあります。
文字列の末尾や先頭に何か足すとコピーが発生しますし、リストの末尾以外に要素を追加してもコピーが発生します。
それを避けるために、リストの末尾に要素を追加してから、"".join で文字列化しています。
# シンプルと関数型的の組み合わせ
s = "stressed"
l = []
for c in reversed(s):
l.append(c)
"".join(l) #=> 'desserts'
インデックスで回す
素直にインデックスで回していく方法もあります。
for n in range(len(s)):はインデックスに対してforを回すときの構文の一つです。
len(s)は文字列sの長さを、range(n)は0,...,n-1を走査するためのイテレータを返します。
# reversed相当の処理を自力で行う
s = "stressed"
l = []
for n in range(len(s)):
l.append(s[len(s)-n-1])
"".join(l) #=> 'desserts'
インデックスの取り方は別の方法があります。
enumerate(s)は、n=len(s)として(0,s[0]),...,(n-1,s[n-1])を走査するためのイテレータを返します。
使わない変数という意味を込めて、_も使っています。込めてるだけです。
# reversed相当の処理を自力で行う
s = "stressed"
l = []
for n,_ in enumerate(s):
l.append(s[len(s)-n-1])
"".join(l) #=> 'desserts'
リスト内包表記
リスト内包表記でも書けます。
s = "stressed"
l = [c for c in reversed(s)]
"".join(l) #=> 'desserts'
しかし、よくよく考えるとジェネレータ内包表記でもいいんじゃないかと思えてきます。
s = "stressed"
g = (c for c in reversed(s))
"".join(g) #=> 'desserts'
そうすると、下記の関数型的に戻りますね。
# 関数型的
s = "stressed"
"".join(reversed(s)) #=> 'desserts'
振り返り
好みは人それぞれだと思いますが、私が一番好きな回答は下記です。
# シンプルと関数型的の組み合わせ
s = "stressed"
l = []
for c in reversed(s):
l.append(c)
"".join(l) #=> 'desserts'
for文で逆に回したい感が伝わるのと、素直にリストに要素を追加している点がわかりやすいです。
また、実際の問題を解く際にも、一つ一つの要素に何か処理をして別のリストに入れるという処理をよく書くので、その基本形になっています。
"".join(reversed(s))やs[::-1]はこの問題を解くには最適なのですが、追加の処理を入れにくくなっています。