はじめに
pythonを本格的に始めました。 CとC++をちょこっとやっていたので、Pythonなんてと思っていましたが、リスト内包やその他Pythonらしさコードにつまずいています。まだ定型式は理解出来るのですが、定型式を超えたPythonらしいコードを自分が書けるとは思えないのです。そこで、まずはステップバイステップで、パイソン素人が、玄人のように書けるよう試行してみたいと思います。この記事、その修行の一部とします。
追記: リスト内包の範囲を間違いを教えて頂いたので、以下修正しています。
リスト内包ジェネレータ-式
例題
Write a function that accepts a string, and returns the same string with all even indexed
characters in each word upper cased, and all odd indexed characters in each word lower
cased. The indexing just explained is zero based, so the zero-ith index is even, therefore
that character should be upper cased and you need to start over for each word.
The passed in string will only consist of alphabetical characters and spaces(' '). Spaces
will only be present if there are multiple words. Words will be separated by a single
space(' ').
Examples:
"String" => "StRiNg"
"Weird string case" => "WeIrD StRiNg CaSe"
文字列を引数に持つ関数を作り、偶数インデックスの文字だけ、大文字にします。奇数インデックスの文字は、小文字にします。複数の単語がある場合、スペースがあれば、スペースを入れます。新しい単語をチェックする場合は、インデックス番号は0から再開し、偶数・奇数を判定します。この新たに作った文字列を返します。
最初のコード
def to_weird_case(words):
i = 0
new_string = ""
for char in words:
if char.isspace():
new_string += char
i = 0
else:
if i % 2 == 0:
new_string += char.upper()
else:
new_string += char.lower()
i += 1
return new_string
まずfor文を一文字のcharで回すか、index番号のiで回すか迷いました。結果、charで回した方が簡単なので、このコードにしました。これでこの問題は正解になるのですが、まったくPythonらしくありません。またCとしても美しくないです。プログラマー失格です。気を取り直して、ここから改良を加えていきたいと思います。
Enumerateを利用する
word = "hello"
for i, char in enumerate(word):
print(f"Index {i}: {char}")
Enumerateを利用すれば、indexのiと文字のcharが両方for文で回せます。
パイソン流演算子を利用する
x = 5
result = "Even" if x % 2 == 0 else "Odd"
これで、if else文が一行になりそうです。
まず、Enumerateを利用して、短くしてみます。
def weird_case_single_word(word):
result = ""
for i, char in enumerate(word):
if i % 2 == 0:
result += char.upper()
else:
result += char.lower()
return result
print(weird_case_single_word("hello")) # "HeLlO"
一番上のi=0やその他の処理が一気になくなりました。
リスト内包ジェネレータ-式の利用
文字列 -> リスト内包ジェネレータ-式 -> 文字列という風に考え、Python流演算子を追加してみます。
def weird_case_single_word(word):
return ''.join(char.upper() if i % 2 == 0 else char.lower()
for i, char in enumerate(word))
print(weird_case_single_word("hello"))
単純な例だと、リストを作るようにするには、以下のようでいいはず。短くなりました。
def weird_case_single_word(word):
return [char.upper() if i % 2 == 0 else char.lower()
for i, char in enumerate(word)]
print(weird_case_single_word("hello")
['H', 'e', 'L', 'l', 'O']
複数文字の処理
さて、複数の文字も扱える必要があるので、もう一つループさせる必要があります。これは、最初の引数の文字列から複数の単語に分ける作業をします。split()ですね。
def weird_case(words):
weird_words = []
for word in words.split():
weird_word = ''.join(char.upper() if i % 2 == 0 else char.lower()
for i, char in enumerate(word))
weird_words.append(weird_word)
return ' '.join(weird_words)
print(weird_case("hello world")) # "HeLlO WoRlD"
これでうまく行きました。でもまだ、単純化できますよね?
ネストのリスト内包ジェネレータ-表記
def weird_case(words):
return ' '.join(''.join(c.upper() if i % 2 == 0 else c.lower()
for i, c in enumerate(word))
for word in words.split())
一番最後に、'for word in words.split())をつけ、join()の前に、' 'スペースを開けてみました。
最終コード
def to_weird_case(words):
return ' '.join(''.join(c.upper() if i % 2 == 0 else c.lower()
for i, c in enumerate(word))
for word in words.split())
print(to_weird_case("Weird string case"))
WeIrD StRiNg CaSe
うまく行きました。
考え方として
"for文で複数の単語が存在する文字列をまず単語に切り取り、各単語を更なるfor文と、リスト内包ジェネレータ-表記やパイソン流演算子で奇数偶数を判定し大文字小文字変換の処理をさせ、最後に単語と単語の間にスペースを加えた、新しい文字列を作る。"
でしょうか?
多くのつわものが、同じようなコードを書いていてホットしていますが、Lamdaを利用したコードも見られました。まだ、私にはできませんが、時間があれば、挑戦してみます。
終わりに
プロジェクトでPythonを利用するようになったので、勉強しています。なかなかPythonらしいコードというのがまだ慣れていませんが、日夜切磋琢磨したいと思います。
Happy Hacking!!!