solutions.problem19   A
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 59
dl 0
loc 165
rs 10
c 0
b 0
f 0
wmc 27

6 Methods

Rating   Name   Duplication   Size   Complexity  
A Date.__str__() 0 3 1
A Date.leap_year() 0 9 1
A Date.__add__() 0 23 4
B Date.__le__() 0 16 7
B Date.__init__() 0 13 7
A Date.days_to_a_month() 0 15 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A solve() 0 21 4
1
"""
2
Project Euler Problem 19: Counting Sundays
3
==========================================
4
5
.. module:: solutions.problem19
6
   :synopsis: My solution to problem #19.
7
8
The source code for this problem can be
9
`found here <https://bitbucket.org/nekedome/project-euler/src/master/solutions/problem19.py>`_.
10
11
Problem Statement
12
#################
13
14
You are given the following information, but you may prefer to do some research for yourself.
15
16
* 1 Jan 1900 was a Monday.
17
*
18
  | Thirty days has September,
19
  | April, June and November.
20
  | All the rest have thirty-one,
21
  | Saving February alone,
22
  | Which has twenty-eight, rain or shine.
23
  | And on leap years, twenty-nine.
24
* A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (107/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
25
26
How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
27
28
Solution Discussion
29
###################
30
31
We could use Python's datetime module, but that feels too much like cheating :raw-html:`&#9786;`
32
33
Instead, the above information will be used to count the number of Sundays falling on the first of the month.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (109/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
34
35
Solution Implementation
36
#######################
37
38
.. literalinclude:: ../../solutions/problem19.py
39
   :language: python
40
   :lines: 44-
41
"""
42
43
44
class Date(object):
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
45
    """ A simple date abstraction representing a dd/mm/yyyy
46
47
    .. note:: the parameters are numbered in the natural way; i.e. ``day`` takes the range :math:`[1,31]`, ``month``
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (116/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
48
              takes the range :math:`[1,12]` and ``year`` takes the range :math:`[0, 9999]`.
49
50
    :param day: the initial date value (day, or dd)
51
    :param month: the initial date value (month, or mm)
52
    :param year: the initial date value (year, or yyyy)
53
    """
54
55
    def __init__(self, day: int, month: int, year: int):
56
        # Basic parameter validation
57
        assert isinstance(day, int), "day must be an integer"
58
        assert isinstance(month, int), "month must be an integer"
59
        assert isinstance(year, int), "year must be an integer"
60
        assert 1 <= day <= 31, "day must be in [1, 31]"
61
        assert 1 <= month <= 12, "month must be in [1, 12]"
62
        assert 0 <= year <= 9999, "year must be in [0, 9999]"
63
64
        # Store the initial date provided
65
        self.day = day
66
        self.month = month
67
        self.year = year
68
69
    def __str__(self):
70
        """ String representation of a Date object """
71
        return "{}/{}/{}".format(self.day, self.month, self.year)
72
73
    def __le__(self, other: 'Date') -> bool:
74
        """ Test whether this ``Date`` instance is less than or equal to the `other` ``Date`` instance
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (102/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
75
76
        :param other: the ``Date`` instance to compare against
77
        :return: ``True`` if the current ``Date`` is equal to or before the `other` ``Date`` instance
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (101/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
78
        :return: ``False`` otherwise
79
        """
80
81
        if isinstance(other, Date):
82
            if self.year < other.year:
83
                return True  # earlier year
84
            elif self.year == other.year and self.month < other.month:
85
                return True  # same year, earlier month
86
            elif self.month == other.month and self.day < other.day:
87
                return True  # same year, same month, earlier day
88
        return False  # any other case
89
90
    def __add__(self, other: int) -> 'Date':
91
        """ Addition operator
92
93
        This class allows for integer values to be added to it. Semantically, this represents adding that integer number
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (120/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
94
        of days to the current date value stored. The new date value will be returned by this operator.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
95
96
        :param other: the number of days to add to this ``Date`` instance
97
        :return: a new ``Date`` instance with `other` days added to the current value
98
        """
99
100
        if not isinstance(other, int):
101
            raise TypeError("unsupported operand type(s) for +")
102
103
        rv = Date(self.day, self.month, self.year)  # build new Date instance with the same value
0 ignored issues
show
Coding Style Naming introduced by
The name rv does not conform to the variable naming conventions ((([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
104
        if rv.day + other > Date.days_to_a_month(rv.month, rv.year):
105
            other -= Date.days_to_a_month(rv.month, rv.year) - rv.day
106
            rv.day = 0
107
            rv.month += 1
108
        if rv.month > 12:
109
            rv.month = 1
110
            rv.year += 1
111
        rv.day += other
112
        return rv
113
114
    @staticmethod
115
    def leap_year(year: int) -> bool:
116
        """ Test whether the given `year` is a leap-year
117
118
        :param year: the year to test
119
        :return: ``True`` if `year` is a leap-year, ``False`` otherwise
120
        """
121
122
        return ((year % 4) == 0) and (((year % 100) != 0) or ((year % 400) == 0))
123
124
    @staticmethod
125
    def days_to_a_month(month, year):
126
        """ Compute the number of days in the month and year provided
127
128
        :param month: the given month
129
        :param year: the given year
130
        :return: the number of days in the given month and year
131
        """
132
133
        standard_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
134
135
        if month == 2 and Date.leap_year(year):
0 ignored issues
show
unused-code introduced by
Unnecessary "else" after "return"
Loading history...
136
            return 29  # February has 29 days in a leap-year
137
        else:
138
            return standard_days[month - 1]
139
140
141
def solve():
142
    """ Compute the answer to Project Euler's problem #19 """
143
144
    lower_bound = Date(1, 1, 1901)
145
    upper_bound = Date(31, 12, 2000)
146
    current_date = Date(1, 1, 1900)
147
    answer = 0
148
149
    current_date += 6  # first Sunday after 1/1/1900
150
151
    # Skip over Sundays, one week at a time, until 1/1/1901
152
    while current_date <= lower_bound:
153
        current_date += 7  # next Sunday
154
155
    # Skip over Sundays, one week at a time, until 31/12/2000
156
    while current_date <= upper_bound:
157
        if current_date.day == 1:
158
            answer += 1  # this is a Sunday and the first of a month
159
        current_date += 7
160
161
    return answer
162
163
164
expected_answer = 171
0 ignored issues
show
Coding Style Naming introduced by
The name expected_answer does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
165