毎朝Codewars@2019.05.29(水): Scramblies
毎朝ちびちびCodewars。
[5 kyu] Scramblies www.codewars.com
今日のお題:
""" scramble(s1, s2) 文字列s1 を構成する文字を使って 文字列s2 が作れるならTrue、そうでないなら Falseを返す。 構成文字は1回しか使えない(s2に'a'が2回出現するなら、s1に2個以上'a'がないとNG) Args: s1 (str): 小文字a-z からなる文字列 s2 (str): 小文字a-z からなる文字列 Returns: 判定結果(Boolean) """
コード
me1: count()で文字の出現数を数えて比較
def scramble(s1, s2): for c2 in set(s2): if s2.count(c2) > s1.count(c2): return False return True
me2: index()関数で該当する文字の位置を取得する
# index()関数で該当する文字の位置を取得する方法 def scramble(s1, s2): ls1 = list(s1) for c2 in s2: try: ls1[ls1.index(c2)] = '*' except ValueError: return False return True
- 最初この方法で書いたのだけど、ランダムテストがタイムアウトして突破できなかった
印象に残った他ユーザのコード
from collections import Counter def scramble(s1,s2): # Counter basically creates a dictionary of counts and letters # Using set subtraction, we know that if anything is left over, # something exists in s2 that doesn't exist in s1 return len(Counter(s2)- Counter(s1)) == 0
- この解法をBest PracticesやCleverにしてる人が多かった。確かにクレバーだと思ったけど、ちょっと納得いかなかった。
- というのも timeitで処理時間を計測すると、文字列が短い時はcount()やindex()を使う方法の倍ぐらい時間がかかる。当然ランダムテストの制限時間に収まらない(タイムアウトする)はずなんだが、実際やってみるとなぜかindex()を使う方法だとタイムアウトして、Counter()方式は問題なく時間内にパスするんだよね。
6/1(土)の #rettypy でこの話をし、
あ、そうか、処理対象の文字数が一定数を超えると処理時間が逆転するのか!
アルゴリズムを変えて実行時間を計測
- 4パターンの解決方法について文字列が短い時(20文字)、長い時(2000文字)のそれぞれについて処理時間がどうなるかを実測した。
- count()で文字列s1, s2の各文字の出現回数を数えて比較する方法
- 上記と同じことをcollections.Counter()を使って行う方法
- index()関数で該当する文字の位置を取得し、"*"に置き換えていく方法
- 3と同じことを、forを愚直に回して該当する文字の位置を取得する方法
- 結果:
短い文字列のうちは「index()関数で該当する文字の位置を取得する方法」の方がCounter()を使う方法よりも遙かに速いが、長い文字になると逆転する。ランダムテストでは文字数6000文字でチェックしていたから、この方法ではパスしないことがわかる。
- 実測だけでなく、ソースを読んで時間計算量を見積もるとベターなんだが時間がとれなかったのでtimeitだけで済ませた。
文法、関数・メソッドnote
collections.Counter([iterable-or-mapping])
collections --- コンテナデータ型 — Python 3.7.3 ドキュメント
- ハッシュ可能なオブジェクトをカウントする dict のサブクラス
from collections import Counter c1 = Counter('gallahad') c2 = Counter({'red': 4, 'blue': 2}) print(c2['red']) # -> 4 # 存在しない要素に対して KeyError の代わりに 0 を返す print(c2['green']) # -> 0 # 0をセットしても要素は取り除かれない。取り除くにはdelを使う c2['blue'] = 0 # counter entry with a zero count print(c2['blue'], c2) # ->0 Counter({'red': 4, 'blue': 0}) del c2['blue'] print(c2['blue'], c2) # -> 0 Counter({'red': 4})
str.count(sub[, start[, end]])
https://docs.python.org/ja/3/library/stdtypes.html?highlight=count#str.count - 部分文字列 sub が重複せず出現する回数を返す。 - スライス表記同様の書き方[start, end] で評価対象範囲を指定できる。
list.index(x[, start[, end]])
https://docs.python.org/ja/3/tutorial/datastructures.html - リスト中で x と等しい値を持つ最初の要素の位置をゼロから始まる添字で返す。 - スライス表記同様の書き方[start, end] で評価対象範囲を指定できる。 - 該当する要素がなければ ValueError になる。