LoginSignup
4
1

More than 5 years have passed since last update.

python Checkio.org Stage: SendGridを解く その1(ほぼ他人の解答への理解を試行錯誤)

Last updated at Posted at 2018-02-18

参考元

Q.1 Stressful Subject

休みとるぜ!だから休暇中のストレスを避けるためにA stressful subjectを含むメール以外無視する関数を作ってくれとのこと。
A stressful subjectを認識する関数を作るために、
A stressful subjectとは、
・メールが全部大文字のとき
・!が3つ以上連続した時
・help, asap, urgentのいずれかを含んだ時。こいつらをred wordsと定義する
・ "HELP", "help", "HeLp", "H!E!L!P!", "H-E-L-P", even in a very loooong way "HHHEEEEEEEEELLP"のような違った形式でもred wordsとみなす。

Input:
Subject lines as a String
Output:
Boolean
Precondition:
subject can be up to 100 letters
Example:

is_stressful("Hi") == False
is_stressful("I neeed HELP") == True

自分の回答:

def redword(word):
    for i in range(len(word)):
        if word[i] == "help" or word[i] == "asap" or word[i] == "urgent":
            return True
        elif "h" in word[i] and "e" in word[i] and "l" in word[i] and "p" in word[i]:
            return True
        elif "a" in word[i] and "s" in word[i] and "p" in word[i]:
            return True
        elif "u" in word[i] and "r" in word[i] and "g" in word[i] and "e" in word[i] and "n" in word[i] and "t"in word[i]:
            return True
    return False

def is_stressful(subj):
    """
        recoognise stressful subject
    """
    print(subj.isupper())
    if subj.isupper():
        return True
    else:
        word = subj.lower().split(" ")
        if word[-1].count("!") >= 3:    #check !!!
            #if [i for i in word[-1] if i == "!")]
            if (word[-1][-1] + word[-1][-2] + word[-1][-3]) == "!!!":
                return True
            else:   #red word
                return redword(word)
        else:   #red word
            return redword(word)

inで強引に調べた。正直ものすごいガバガバ。h,e,l,pの順番を気にしてないから、こいつらを含む他の単語でもTrueを返してしまう。問題のcheckは通ってしまったから載せるが…。関門ガバガバすぎない?
↓↓↓↓↓

他人のcreativeなAnswer

試しに人のコードを見たらなんかスゲェ。
引用元:
https://py.checkio.org/mission/stressful-subject/publications/review/puzzle/

is_stressful = lambda subject: any([subject.endswith("!!!"), subject == subject.upper(),
                                   *[r in "".join(c for c, d in zip(subject.upper(), subject[1:] + " ")
                                     if "A" <= c <= "Z" and c != d) for r in ("HELP", "ASAP", "URGENT")]])  

3行?実質1行じゃねぇか。頭おかしいよ。
同じ問題を俺たちは解いてるんだよな?というレベル。理解しようと試みる。
今日はこの意味ふみこを整理しよう。

分解と整理をすればなんとかなると信じろ

lambda式

lambdaはPyhtonにおいて無名関数と呼ばれる。ラムダと読む。
その名の通り、名前のない関数を作成できる。(名前をつけることもできる)
使い捨てで、二度と使わない関数とかに気軽に使える。都合のいい女or男or神みたいなやつ。
参考元:https://www.lifewithpython.com/2013/01/python-anonymous-function-lambda.html
lambda式のフォーマットはa_function名 = lambda x:yxが引数。yが戻り値。

a_function = lambda x: x **2
a_function(10) #100
a_function(3) #9

上記lambda式は

def a_function(x):
    return x ** 2

と書くのと同じ。
以下実践例:lambda (ラムダ)とsorted()

sorted()

sorted() ohこんな便利な関数があるのを知らんかった。与えられたリストをソートしてくれる関数。元リストは変更せず、並び替えたリストのコピーを返す。任意のイテラブルを受け付ける。
参考元:https://docs.python.jp/3/howto/sorting.html 
上記はsorted()に関する公式ドキュメント。
sorted()に関連してkeyパラメータがある。keyパラメータは単一の引数をとり、ソートに利用される。keyの指定にはソートに使われるkeyを返す必要がある。

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

lambda直後のstudentは引数名だからなんでもいい。:の次のstudent[2]はkeyに引数studentのstudent[2]を戻り値として渡している。この渡した値を基準にsotred()はソートを行う。この場合は、数字を小さい→大きい順。昇順。

any()

any(iterable)iterableのいずれかの要素が真ならばTrueを返す。
iterableが空ならFalseを返す。iterableってなんだよ…。リストとか、
iterableに関する公式ドキュメント:https://docs.python.jp/3/glossary.html#term-iterable 
iterableだけ全部英語。えぇ…。
要約すると、all sequence types (such as list, str, and tuple)はiterable。かつsome non-sequence types like dict, file objectsもiterable。以下略。いまは深く突っ込みたくない。沼やぞ沼。謎が謎を呼ぶぞ。Dive Into Pythonの章にiterableに関するのあったから、そっち見てから深く切り込んでいけ。

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

と同じ意味。以下例

>>> any([False, ""])
False
>>> any([1, False, ""])
True

シーケンス内のいずれかの要素が真ならば、Trueを返す。各データ型におけるbool値の振る舞いを参照してる希ガス。Dive Into Pyhtonで軽く触ったけどあやふやですわ。
bool値のFalseのみがany()の中にあってもFalse。

参考元:
https://docs.python.jp/3/library/functions.html?highlight=any#any

endswith()

endswith()関数は文字列の末尾から文字列を検索できる。オプションで開始位置, 終了位置の指定をできる。Bool値を戻す。
endswiht("!!!")で末尾に"!!!"があるか調べている。

str.upper()strを大文字にする。str.isupper()でstrが大文字化判定。True,Falseで返す。

他人コードをもう一回ちょっと分解してみてみる。

 *[r in "".join(c for c, d in zip(subject.upper(), subject[1:] + " ") if "A" <= c <= "Z" and c != d) for r in ("HELP", "ASAP", "URGENT")]]

パット見じゃわけわかめ。

*[] 可変長引数

まず*[]が調べても分からねぇ。可変長引数?
参考元:
http://note.crohaco.net/2014/python-argument-intro/
*は複数の値を持ちうる引数を展開する。あるいは、引数が複数の値を受け取れるようにするとのこと(可変長引数)。
*はリストやタプルの前につくと、リストやタプルを通常引数、デフォルト引数、可変長引数に対して展開します。←ってどういう意味やねん。リストやタプルとかを、リストやタプルじゃない形式で使えるようにするってことか?
**と2つの場合は、辞書をデフォルト引数」「キーワード可変長引数」に対して展開する。

>>> def apply_test(normal, default=1, *args, **kwargs):
...     print normal
...     print default
...     print args
...     print kwargs
... 
>>> args = [1, 2, 3, 4]
>>> # *での展開は「通常引数」「デフォルト引数」「可変長引数」に対して行われる
>>> apply_test(*args)
1
2
(3, 4)  #可変長引数でうけとったからタプル。
{}

apply_test(args)において、*argsが展開(=リストの各要素ごとにバラバラになる)されて、normalにargs[0], defaultにargs[1], 仮引数argsにargs[2]と[3]が各々格納された。args[2],args[3]がタプルに格納された理由は↓
apply_test()の定義において、仮引数として*argsが使われてる。このargs前についた`
が、引数を可変長引数に変化させる。引数の前に`をつけると、受け取った引数を順番にタプルに格納する。
咀嚼して消化するのにちょっと時間がかかりますん。
今回の場合、
[]はどういう扱いなんだよぉ!?←つまり、引数の展開の*[]

join()

str.join()関数。strを区切り文字として使って、引数のシーケンスを連結する。
"区切り文字".join(リスト)の形式

>>> "".join(["a","b","c","命"]
'abc命'
>>> "siri".join(["a","b","c","乳"])
'asiribsiricsiri乳'

本題に戻って、join()の中身
c for c, d in zip(subject.upper(), subject[1:] + " "を見る。
多分これは昨日やったタプル内包表記???違うかも。

zip():
zip()複数のシーケンスをまとめてループさせるのに使う。要素数が違う場合は一番少ないものに合わせてループを行う。
参考元:https://python.civic-apps.com/zip-enumerate/

>>> l1 = [1,2,3]
>>> l2 = [4,5,6]
>>> for (a, b) in zip(l1, l2): #l1,l2を同時ループ
...   print(a, b)
... 
1 4
2 5
3 6
>>> list3 = ["oppa", "siri"]
>>> for (a, b) in zip(l1, list3):  #要素数の少ないlist3に合わさる
...  print(a, b)
... 
1 oppa
2 siri
>>> l4 = [
... [1,2,3],
... [4,5,6],
... [7,8,9]]
>>> l4
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> for (a,b,c) in zip(*l4):    
...   print(a,b,c)
... 
1 4 7
2 5 8
3 6 9
  1. コレ最後は何が起こってんの?あー。まず*[]でリストが分解される。 リストが分解されて[1,2,3],[4,5,6],[7,8,9]がでてくる。3つのリストがzip()にはいったてこと。 この3つのリストをzip()で回したことになる。各リストのindex0番目から順番にa,b,cに入る。故に最初は1,4,7が出力される。

最終整理

また本題に戻って、

any(
*[r in "".join(c for c, d in zip(subject.upper(), subject[1:] + " ") if "A" <= c <= "Z" and c != d) for r in ("HELP", "ASAP", "URGENT")]])
  1. any()のなかのjoin()の中から順番に見ていく。join()の中で変数cにおけるリスト内包表記が行われる。
  2. subject.upper()。subjectはlamda式で定義されるis_stressful関数の引数。lambda subject:any()のsubject。この引数subjectをupper()で大文字にしている。このsubjectの大文字化された文字列は、zip()で使われる1つめのシーケンス。
  3. 次のsubject[1:]+""は引数subjectをindex1からindexの末尾までスライスして、" "を文字列結合させたもの。zip()で使われる2つめのシーケンス。
  4. 上記シーケンス2つがzip()の複数のシーケンス。2つのシーケンスは同じ要素数。ゆえにfor c, d in zip()において、cはsubject[0]から順番に返し。dはsubject[1]から末尾" "までを順番に返す。
  5. (c ~ c != d)内部は内包表記何じゃないか?と考える。理由は変数cが前にでて新しいリストをfor文の値によって作ろうとしている。また、if文でその値を選別しようとしているよう見えるから。
  6. 変数cにおけるfor文の後のif節を見る。変数c(subject[0]からのシーケンス)がAからZまでのどれかのアルファベットであり、且つ c != dの2つのif節条件を満たした時、cの値のリストが作られる。…はず。
  7. なぜif節でA <= c <= Zかつc != dとしたのかは、red wordsが"Help","ASAP","URGENT"であるからだ。どれもA-Zのアルファベット(if節の1つめの条件)で、かつ同じアルファベットが連続していない単語である(if節の2つめの条件)。
  8. "".join()で5.のリスト内包表記によって作られた変数cの値からなるリストが結合される。
  9. そしてもう1回変数rにおけるリスト内包表記が行われる。*[r in "".join() for r in ("HELP", "ASAP", "URGENT")]。ここで俺が最初見て混乱したのは、1回目のinと、2回目のinがの仕事が違うことだ。1回目のinはTrue, Falseの返す検索のinだ。2回めのfor文内のinは範囲を示す。 つまり変数rは"HELP", "ASAP", "URGENT"を1つずつ取り出した値を一時格納する。それは文頭のrとして使われる。最終的にr in "".join()となる。"".join()の戻り値は変数cの値のリストが結合したものだ。それらの中に"HELP", "ASAP", "URGENT"があるかをinで調べている。
  10. r in cの文字結合はリストの形で返ってくる。そのリストは3つの要素を持つ。"HELP","ASAP","URGENT"と3つだからだ。True,Falseのどちらかが格納されたリストだ。このリストは大きなリストのなかの1つの値だ(元のコードをみると分かる)。ゆえに新しく作られたリストを*[]で展開し、大きなリストの要素として格納し直している。any()の引数用に,1つのリストに要素を入れ直していると言い帰れる。
  11. any()が値を評価する。シーケンスの中のそれぞれのデータ型において。ブール値によるふるまいでTrueをかえす値が1つでも存在すれ場合。このany()はTrueを戻り値に返す。逆にFalseを返すなら、1つもTrueに振る舞うデータ型がなかったてことだ。

最後にもう1回同じコード。

is_stressful = lambda subject: any([subject.endswith("!!!"), subject == subject.upper(),
                                   *[r in "".join(c for c, d in zip(subject.upper(), subject[1:] + " ")
                                     if "A" <= c <= "Z" and c != d) for r in ("HELP", "ASAP", "URGENT")]])   

以上自分用のむしゃむしゃそしゃく解説終わり。

わかったこと

・問題を解決する具体的な案がかっこいいほどコードもかっこよくなる
・メソッドと関数を知るのにhttps://docs.python.jp/3/のPython3.6.3
ドキュメントがやべぇ役に立つ
・googleは神。

まとめ と 自分で再作成

何かかっこいい文を自分でも分かるようダサいコードにするとこうなる


def is_stressful(subject):
    if subject.endswith("!!!"):    
        return True
    l1 = [c for c, d in zip(subject.upper(), subject[1:]+" ") if "A" <= c <= "Z" and c != d]     
    red_words = ["HELP", "ASAP", "URGENT"]
    if any([subject.isupper(), *[r in "".join(l1) for r in red_words]]):     #*[]を無理矢理でも使いたかった。any()の外にsubject.upper()を出せば。r直前の*はいらない。
        return True
    else:
        return False
  1. str.endswith()は語尾から文字列を調べてくれる関数。str.stratswith()は文頭から文字列を調べてくれる。
  2. str.isupper()はアルファベットの大文字か調べてくれる。str.upper()なら大文字にしてくれる。if subject == subject.upper()としてもいい。

3.1 リスト内包表記。昨日(一昨日)dive into python第3章でやった。最初のcに対していくら複雑な関数でも適用することできる。変数cを引数に使われた関数の戻り値のリストを作成してくれる。使うとコードがスッキリと圧縮される。if節をfor文の後ろにつけて、作られるリストへ格納される値を選別できる。

3.2 zip()はfor文を複数のシーケンスではしらせることができる。要素数が違う場合は、少ない方に合わせてforループを行う。

3.3 リスト内包表記によって、cのリストが作られる。cの値はfor文から受け取り。if節で選別される。

4.1 ここもリスト内包表記。作られたリストは、any()関数の引数として使われる。各データ型のブール値の振る舞いでTrueが1つでも存在すれば、Trueを戻り値にする。

4.2 []は可変長の引数を展開する。言い換えると、,で区切られた形の引数として扱うことができるようになる。
例1) any([1,2,3,4,5])のままならリストとして扱われる。
例2) `any(
[1,2,3,4,5])ならany(1,2,3,4,5)`と等価。←これエラー出るけど。

4.3 リスト内包表記の変数に対して、あらゆる関数を使うことが出来る。ここではin。l1の結合された文字列に対して、リストred_wordsの中身でin検索を書けている。その結果はリストとして新しく作成される。*[]ゆえに展開され、any()の引数として使われる。

これany()に関して間違ったこと書いてるぞ。any()はiterableな引数1つしかとることができないっぽい。any(1,2,3,4,5)とかerrorっぽいぞ。

以上。まとめおわり。
問題は1つしか進まなかったし、わけわからん他人の解答見て頭爆発してフリーズしたけど僕は元気です。

終わりに。

毎日投稿\(^o^)/オワタ。ブドウ味のアメがおいしい。ブドウ好き。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1