時間帯重複チェック(応用編)にPythonで回答

 応用編(お題:時間帯重複チェック(応用編) - No Programming, No Life)が掲載されていたので挑戦。
 基本的な方針としては、期間を表現するクラスを定義する。あとはどうにかする。
 実装後の感想としては、期間のコレクションクラスを定義したほうが綺麗にできそう。テストコードを分けたほうが読みやすくなりそう。
 とりあえずテストコードだけでも書いておけば、作業を中断してもどこができてないかすぐに分かるので便利。

# -*- coding: utf-8 -*-
# http://d.hatena.ne.jp/fumokmm/20110326/1301146382
# http://d.hatena.ne.jp/fumokmm/20110329/1301403400

import datetime
import unittest


class TimeDuplecationCheck2TestCase(unittest.TestCase):
    def testExample1(self):
        result = timeDuplicationCheck2((12, 0, 13, 0),
                                       (10, 0, 12, 15))
        self.assertEquals(1, len(result))
        self.assertEquals((12, 0, 12, 15), result[0])

    def testExample2(self):
        result = timeDuplicationCheck2((16, 0, 23, 0),
                                       (9, 0, 17, 0),
                                       (5, 0, 10, 30))
        self.assertEquals(2, len(result))
        self.assertEquals((9, 0, 10, 30), result[0])
        self.assertEquals((16, 0, 17, 0), result[1])

    def testExample3(self):
        result = timeDuplicationCheck2((12, 0, 23, 0),
                                       (13, 0, 14, 0),
                                       (15, 0, 16, 0),
                                       (17, 0, 18, 0),
                                       (19, 0, 20, 0),
                                       (21, 0, 22, 0))
        self.assertEquals(5, len(result))
        self.assertEquals((13, 0, 14, 0), result[0])
        self.assertEquals((15, 0, 16, 0), result[1])
        self.assertEquals((17, 0, 18, 0), result[2])
        self.assertEquals((19, 0, 20, 0), result[3])
        self.assertEquals((21, 0, 22, 0), result[4])

    def testExample4(self):
        result = timeDuplicationCheck2((10, 0, 12, 0),
                                       (11, 0, 11, 30),
                                       (10, 30, 11, 15))
        self.assertEquals(1, len(result))
        self.assertEquals((10, 30, 11, 30), result[0])

    def testExample5(self):
        result = timeDuplicationCheck2((9, 0, 17, 0),
                                       (19, 0, 21, 0))
        self.assertEquals(0, len(result))

    def testValueError(self):
        self.assertRaises(ValueError, timeDuplicationCheck2, None)
        self.assertRaises(ValueError, timeDuplicationCheck2, (0, 0, 0, 0))
        

def timeDuplicationCheck2(*args):
    if 1 == len(args):
        raise ValueError
    result = []
    for tuple1 in args[:-1]:
        period1 = Period(tuple1)
        for tuple2 in args[args.index(tuple1) + 1:]:
            period2 = Period(tuple2)
            if period1.isDuplicated(period2):
                result.append(period1.duplicatedPeriod(period2))
            else:
                pass
    temp = []
    for period in result:
        if len(temp) == 0:
            temp.append(period)
        elif temp[-1].isDuplicated(period):
            temp[-1] = temp[-1] + period
        else:
            temp.append(period)
    result = temp
    return sorted([p.asTuple() for p in result])

        
class TimeDuplecationCheckTestCase(unittest.TestCase):
    def testTimeDuplicationCheck(self):
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (-1, 0, 0, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (25, 0, 0, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, -1, 0, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 60, 0, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (24, 1, 0, 0), (0, 0, 0, 0))
        
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, -1, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 25, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, -1), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 60), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 24, 1), (0, 0, 0, 0))
        
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (-1, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (25, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, -1, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 60, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (24, 1, 0, 0))

        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 0, -1, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 0, 25, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 0, 0, -1))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 0, 0, 60))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 0, 24, 1))

        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 1, 0, 0), (0, 0, 0, 0))
        self.assertRaises(ValueError, timeDuplicationCheck,
                          (0, 0, 0, 0), (0, 1, 0, 0))

        self.assertFalse(timeDuplicationCheck((0, 0, 24, 0), (24, 0, 24, 0)))
        self.assertTrue(timeDuplicationCheck((0, 0, 24, 0), (0, 1, 23, 59)))
        self.assertFalse(timeDuplicationCheck((1, 0, 5, 30), (9, 0, 23, 0)))
        self.assertFalse(timeDuplicationCheck((1, 0, 2, 0), (2, 0, 3, 0)))
        self.assertTrue(timeDuplicationCheck((1, 0, 2, 1), (1, 59, 3, 0)))

        return


def timeDuplicationCheck(period1, period2):
    period1 = Period(period1)
    period2 = Period(period2)
    return period1.isDuplicated(period2)


class PeriodTestCase(unittest.TestCase):
    def setUp(self):
        self._allday = Period((0, 0, 24, 0))
        
    def testInit(self):
        self.assertRaises(ValueError, Period, None)

    def testEq(self):
        self.assertEquals(self._allday, Period((0, 0, 24, 0)))
        self.assertEquals(Period((9, 0, 18, 0)), Period((9, 0, 18, 0)))
        self.assertNotEqual(self._allday, 0)
        self.assertNotEqual(self._allday, None)

    def testAdd(self):
        self.assertEquals(None, Period((0, 0, 12, 0)) + Period((12, 0, 24, 0)))
        self.assertEquals(Period((0, 0, 24, 0)),
                          Period((0, 0, 12, 1)) + Period((12, 0, 24, 0)))
        
    def testDuplicatedPeriod(self):
        self.assertEquals(Period((9, 0, 12, 0)),
                          self._allday.duplicatedPeriod(Period((9, 0, 12, 0))))

    def testAsTuple(self):
        self.assertEquals((0, 0, 24, 0), self._allday.asTuple())
        
            
class Period(object):
    def __init__(self, period):
        if not isinstance(period, tuple):
            raise ValueError
        if not 4 == len(period):
            raise ValueError
        for value in period:
            if not isinstance(value, int):
                raise ValueError
        if not 0 <= period[0] <= 24:
            raise ValueError
        if not 0 <= period[1] <= 59:
            raise ValueError
        if not 0 <= period[2] <= 24:
            raise ValueError
        if not 0 <= period[3] <= 59:
            raise ValueError
        if 24 == period[0] and not 0 == period[1]:
            raise ValueError
        if 24 == period[2] and not 0 == period[3]:
            raise ValueError
        self._begin = createTime(period[0], period[1])
        self._end = createTime(period[2], period[3])
        if self._end < self._begin:
            raise ValueError

    def isDuplicated(self, other):
        return not None == self.duplicatedPeriod(other)

    def duplicatedPeriod(self, other):
        if not isinstance(other, Period):
            raise ValueError
        result = Period((0, 0, 0, 0))
        if self._end <= other._begin or other._end <= self._begin:
            return None
        if other._begin < self._begin:
            result._begin = self._begin
        else:
            result._begin = other._begin
        if self._end < other._end:
            result._end = self._end
        else:
            result._end = other._end
        return result

    def __eq__(self, other):
        if None == other:
            return False
        if isinstance(other, Period):
            return self._begin == other._begin and self._end == other._end
        return False

    def __add__(self, other):
        if not self.isDuplicated(other):
            return None
        result = Period((0, 0, 0, 0))
        if self._begin < other._begin:
            result._begin = self._begin
        else:
            result._begin = other._begin
        if self._end < other._end:
            result._end = other._end
        else:
            result._end = self._end
        return result

    def asTuple(self):
        tomorrow = datetime.datetime.today() + datetime.timedelta(days=1)
        tomorrow = datetime.datetime(tomorrow.year,
                                     tomorrow.month,
                                     tomorrow.day)
        result = [0, 0, 0, 0]
        if self._begin == tomorrow:
            result[0] = 24
            result[1] = 0
        else:
            result[0] = self._begin.hour
            result[1] = self._begin.minute
        if self._end == tomorrow:
            result[2] = 24
            result[3] = 0
        else:
            result[2] = self._end.hour
            result[3] = self._end.minute
        return tuple(result)
    
    
def createTime(hour, minute):
    today = datetime.datetime.today()
    if hour == 24 and minute == 0:
        tomorrow = today + datetime.timedelta(days=1)
        return datetime.datetime(tomorrow.year, tomorrow.month, tomorrow.day,
                                 0, 0)
    return datetime.datetime(today.year, today.month, today.day,
                             hour, minute)


def main():
    unittest.main()

if __name__ == '__main__':
    main()