毎朝Codewars@2019.05.01(水): Catching Car Mileage Numbers
令和初日の今朝もちびちびCodewars。
Catching Car Mileage Numbers [4級] www.codewars.com
今日のお題:
def is_interesting(number, awesome_phrases): """3桁以上の数字で、次の6つのいずれかに該当したら「面白ナンバー (Interesting numbers)」とする。: 1. 先頭が任意の数字で残りは全部 '0': Any digit followed by all zeros: 100, 90000 2. 全桁同じ数値: Every digit is the same number: 1111 3. 右隣の数値は+1したもの(9の次は0): The digits are sequential, incementing†: 124 4. 右隣の数値は-1したもの(1の次は0): The digits are sequential, decrementing‡: 4321 5. 回文: The digits are a palindrome: 1221 or 73837 6. リストawesome_phrasesに含まれる: The digits match one of the values in the awesome_phrases array † For incrementin sequences, 0 should come after 9, and not before 1, as in 7890. ‡ For decrementing sequences, 0 should come after 1, and not before 9, as in 3210. Args: number (0 <int <1,000,000,000): should be > 99 awesome_phrases (list): 6番目のテストで使う照合用リスト Returns: 2: number自身が面白ナンバー 1: number+1か+2した値が面白ナンバー 0: boring (いずれにも該当せず)
コード
me
def is_interesting(number, awesome_phrases): def check_1(str_number): """ 末尾が0+か """ if str_number.rstrip('00') == str_number: return False return True def check_2(str_number): """ 全桁が同じ数値か """ for s in str_number: if s != str_number[0]: return False return True def check_3(str_number): """ 順に増える数値 1234567890 """ inc = '1234567890' for i in range(len(str_number)-1): if str_number[i+1] != inc[int(str_number[i])]: return False return True def check_4(str_number): """ 順に減る数値 9876543210 """ dec = '9012345678' for i in range(len(str_number)-1): if str_number[i+1] != dec[int(str_number[i])]: return False return True def check_5(str_number): """ 回文 """ for i, s in enumerate(str_number[:int(len(str_number)/2)]): if s != str_number[-1-i]: return False return True def check_6(str_number, awesome_phrases): """ auesome_phrasesのいずれかにマッチ """ if awesome_phrases == []: return False for awsome in awesome_phrases: if str_number == str(awsome): return True return False # メインの処理 # number が 0以下か、1,000,000,000以上なら0を返してその場で終了 if number > 0 and number >= 1000000000: return 0 for n, ret in [(number,2),(number+1, 1), (number+2, 1)]: if n > 99: str_number = str(n) if check_1(str_number): return ret if check_2(str_number): return ret if check_3(str_number): return ret if check_4(str_number): return ret if check_5(str_number): return ret if check_6(str_number, awesome_phrases): return ret return 0
- サブ関数の命名「check_n」がイケてない。なんのチェックかわからない。
- 設問の意味を取り違えた(check_1)。末尾が 00+ じゃなくて、任意数値(1個)と残りが 0
def check_1(str_number): """ 任意1文字と残りは0 """ if int(str_number[1:]) == 0: return True return False
- check_1からcheck_6までずらずら書いて呼び出すところをなんとかしたかった。awesomeコードを見てやり方がわかった(後述)。
awesome
他ユーザーによるエレガントで印象に残った解法。
https://www.codewars.com/kata/reviews/5759b284d9c9348de3000004/groups/57ec8e71e772823c1d0002c4
def is_incrementing(number): return str(number) in '1234567890' def is_decrementing(number): return str(number) in '9876543210' def is_palindrome(number): return str(number) == str(number)[::-1] def is_round(number): return set(str(number)[1:]) == set('0') def is_interesting(number, awesome_phrases): tests = (is_round, is_incrementing, is_decrementing, is_palindrome, awesome_phrases.__contains__) for num, color in zip(range(number, number+3), (2, 1, 1)): if num >= 100 and any(test(num) for test in tests): return color return 0
- 関数名: 何のチェックかがわかりやすい。
- 「全桁同じ数値」なら必ず「回文」チェックをパスするため、「全桁同じ数値」チェックを省略している。
- ★≫ 複数のtestの回し方 -> この書き方、使えるφ(・ェ・o)~メモメモ
(後述 - ジェネレータ式: リストで一覧した複数の関数を次々と通す 参照)
文法、メソッドnote
変数名の英語
- palindrome: 回文
- incrementing / decrementing:: +1/-1
文字列を反転させる(回文チェックロジック)
スライスで文字列をひっくり返して、元の文字列と一致を取る。
str(number) == str(number)[::-1]
zip(*iterables) 組み込み関数
https://docs.python.org/ja/3/library/functions.html#zip
- 引数として渡された複数のイテラブルオブジェクトから、それぞれの同じi番目の要素をひとまとめ(タプル)で返す。
- 一番短いイテラブルが尽きたら終了
for num, color in zip(range(number, number+3), (2, 1, 1)): pass # zip((number, number+1, number+2), (2, 1, 1)) # num, color = number, 2 # = number+1, 1 # = number+2, 1 が順に渡される。
any(iterable) と all(iterable) 組み込み関数
- any(iter): iterableの要素のどれかが真ならば True を返す。iterable が空なら False を返す。
https://docs.python.org/ja/3/library/functions.html#any - all(iter): 全要素が真ならば True を返す。
https://docs.python.org/ja/3/library/functions.html#all
ジェネレータ式: リストで一覧した複数の関数を次々と通す
tests = (is_round, is_incrementing, is_decrementing, is_palindrome, awesome_phrases.__contains__) if num >= 100 and any(test(num) for test in tests): return color # numが100以上で、かつ # is_round(num), is_incrementing(num), ... のテストのうち # 最低1つでも結果がTrueだったら color 値を返す
- ジェネレータ式 (generator expression) は、通常 丸かっこで囲む。 。
- ( 式 for ターゲットリスト in イテラブルオブジェクト )
こんな具合->(test(num) for test in tests)
- 関数の引数として呼ばれる時は、外側の丸かっこを省略可能。
https://docs.python.org/ja/3/reference/expressions.html#generator-expressions - 構文はリスト内包表記に酷似しているが、内包表記 (comprehension)がリストを生成するのに対し、ジェネレータ式はジェネレータオブジェクトを生成する。計算は要素を取り出すタイミングで行う。
- 逆にいうと、リスト内包表記は最初に全部評価するが、ジェネレータは個々の要素を取り出すときまで計算が遅延する。
- 使いどころ: all, anyとの組み合わせで活きる。
https://www.haya-programming.com/entry/2019/03/02/113446
awesome_phrases.__contains__(num) は何をしているのか?
https://docs.python.org/ja/3/reference/datamodel.html#object.__contains__
- 明確な説明が見つけられなかったのだが、恐らく
帰属テストnum in awesome_phrases
が真なら Trueを返すのだと思われる。
その他
- 課題≫ 効率のよいデバッグ方法、環境整備の調査・検討
- 課題≫ 英文を正確に理解できないと厳しい。
ただし実行例や他人の解を見てから設問を読みかえせば理解できる。設問を注意深く読み込む癖をつけるのと、数をこなしてパターン慣れすればなんとかなりそう。