summer_tree_home

Check iOでPython3をマスターするぜっ

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日ずつループさせるよりも、かなり効率が良い。