Weekend counter (Scientific Expedition) - 週末を数えよ
どんな問題?
Weekend counter
http://www.checkio.org/mission/weekend-counter/
指定した期間に週末日(土曜と日曜)が何日含まれるか数えよ。
引数は、期間の開始日と終了日をdateで受け取る。
戻り値は、土日の回数を数えて返す。
例題:
checkio(date(2013, 9, 18), date(2013, 9, 23)) == 2 checkio(date(2013, 1, 1), date(2013, 2, 1)) == 8 checkio(date(2013, 2, 2), date(2013, 2, 3)) == 2
どうやって解く?
まずは、Python3.3のdateクラス を読んでみた。
dateクラスの weekday() か isoweekday() メソッドを使えばよさそうだ。
weekday()では、月曜が0 ~ 日曜が6
isoweekday()では、月曜が1 ~ 日曜が7 となる。
dateをループさせる方法で少し迷ったが、1日ずつ増やしていくには、
d += timedelta(1)
とするようだ。whileループを使って書いてみた。
from datetime import date, timedelta def checkio(from_date, to_date): count = 0 d = from_date while d <= to_date: # from_dateからto_dateまでループ if d.weekday() in (5, 6): # 土日なら count += 1 d += timedelta(1) # dを1日増やす return count
クリア~!
でも、ちょっとやぼったい。もうちょっとスマートに書きたい。
dateクラスを眺めていたら、toordinal()とfromordinal()というメソッドがあった。dateをintに変換したり、それをdateに戻すことができる。intの値は「先発グレゴリオ暦における日付序数」だそうだ。
これを使って、ループを書いてみた。
def checkio(from_date, to_date): dates = [date.fromordinal(i) for i in range(from_date.toordinal(), to_date.toordinal() + 1)] return len([d for d in dates if d.weekday() in (5, 6)])
しかし、土日を数えるだけで、「先発グレゴリオ暦における日付序数」とやらを使うのも大げさだな。
dateを列挙するrange()関数のようなものがあれば便利なのに。と、思って書いてみたのがこちら。
def get_dates(first, last): # firstからlastまでのdateを列挙(注意:first,lastを含む) for i in range((last - first).days + 1): yield first + timedelta(i) def is_weekend(d): # 土日ならTrue return d.weekday() in (5, 6) def checkio(from_date, to_date): return len([d for d in get_dates(from_date, to_date) if is_weekend(d)])
これで公開。
他の人の答え
最初見たときに理解できなかったのがこちら。
http://www.checkio.org/mission/weekend-counter/publications/veky/python-3/adjustment/
def checkio(d1, d2): w1, w2 = d1.weekday(), d2.weekday() count = (d2 - d1).days // 7 * 2 while True: count += w2 > 4 if w1 == w2: return count w2 = (w2 - 1) % 7
まず、d1とd2の間に何週間があるかを計算して、*2すれば、その間の土日の数がわかる。
count = (d2 - d1).days // 7 * 2
あとは、端数の日に含まれる土日を調べる。
例えば、
月 | 火 | 水 | 木 | 金 | 土 | 日 |
d1 | - | - | - | - | ||
- | - | - | - | - | - | - |
- | - | - | - | - | - | - |
- | - | - | - | - | d2 |
この場合、まるまる3週間が含まれるので、3*2=6回の土日がある。
3週間を除くと、残りはこのようになる。
月 | 火 | 水 | 木 | 金 | 土 | 日 |
d1 | - | - | d2 |
whileループで、短縮したd1~d2(w1~w2)の間の土日を数えている。
while True: count += w2 > 4 # 土日ならcount += 1 if w1 == w2: return count w2 = (w2 - 1) % 7 # w2を1日ずつ前にずらす
この方法だと、全期間を1日ずつループさせるよりも、かなり効率が良い。