The single instance of this class holds the entire collection of years and months.
# - - - - - c l a s s Y e a r C o l l e c t i o n
class YearCollection:
'''Represents the entire set of years and months.
Exports:
YearCollection():
[ return a new, empty YearCollection instance ]
.addYear ( yyyy ):
[ yyyy is a year number as a four-digit string ->
self := self with a new, empty YearRow
added for year=yyyy
return that YearRow ]
.__getitem__(self, yyyy):
[ yyyy is a year number as a four-digit string ->
if yyyy is contained in self ->
return the corresponding YearRow instance
else -> raise KeyError ]
.genYearsRev():
[ generate self's contained YearRow instances in
reverse chronological order ]
.neighbors ( yyyy_mm ):
[ yyyy_mm is a month string as 'yyyy-mm' ->
let:
prev == the month in self prior to yyyy_mm in
chronological sequence as a 'yyyy-mm' string,
or None if first
next == the month in self after yyyy_mm in
chronological sequence as a 'yyyy-mm' string,
or None if last
in:
return (prev,next) ]
.findMonth ( yyyy_mm ):
[ yyyy_mm is a month key string as 'yyyy-mm' ->
if self has a month with that key ->
return the corresponding MonthCell
else -> raise KeyError ]
Inside the instance, we will need someplace to store
the contained YearRow instances.
Because the set of years might have gaps in it, we can
use a dictionary whose keys are year numbers.
State/Invariants:
.__yearMap:
[ a dictionary whose keys are the 'yyyy' strings
of years in self, and each corresponding value
is a YearRow instance for that year ]
'''
All the constructor needs is to set up the invariant on
the .__yearMap attribute by making it
an empty dictionary.
# - - - Y e a r C o l l e c t i o n . _ _ i n i t _ _
def __init__ ( self ):
'''Constructor for YearCollection.
'''
self.__yearMap = {}
This method creates a new YearRow instance
and adds it to self. See Section 22, “class YearRow: Container for one
year's records”.
# - - - Y e a r C o l l e c t i o n . a d d Y e a r
def addYear ( self, txny, yyyy ):
'''Add a new year row
'''
yearRow = YearRow ( self, txny, yyyy )
self.__yearMap[yyyy] = yearRow
return yearRow
This method implements the index ([...])
operation on a YearCollection. If there
is no such year, it will raise KeyError.
# - - - Y e a r C o l l e c t i o n . _ _ g e t i t e m _ _
def __getitem__ ( self, yyyy ):
'''Implements self[yyyy]
'''
return self.__yearMap[yyyy]
# - - - Y e a r C o l l e c t i o n . g e n Y e a r s R e v
def genYearsRev ( self ):
'''Generate years in reverse chronological order
'''
The set of keys to self.__yearMap is the
set of years. We extract it, sort it, reverse
it, and then generate the contained YearRow instances in that order.
#-- 1 --
# [ yyyyList := keys of self.__yearMap in descending
# order ]
yyyyList = self.__yearMap.keys()
yyyyList.sort()
yyyyList.reverse()
#-- 2 --
# [ generate the YearRow instances in self in order by
# the elements of yyyyList ]
for yyyy in yyyyList:
yield self[yyyy]
#-- 3 --
raise StopIteration
For a given month key expressed as a ' string, this method finds the preceding and
following months, and returns a two-element tuple
containing the month keys of its immediate neighbors,
using yyyy-mm'None for the predecessor of the
first month in the set, or for the successor of the last
month.
# - - - Y e a r C o l l e c t i o n . n e i g h b o r s
def neighbors ( self, yyyy_mm ):
'''Find the previous/next months to yyyy_mm in sequence
'''
Because we use a two-level structure of years and months, and because the predecessor or successor of a month may be in a different year, we'll use this algorithm to find the predecessor.
If year yyyy has any months before
mm, the predecessor is the last
such month.
Work backwards through years yyyy-1,
yyyy-2, until we find a year that
has at least one month in it. When we do, the
predecessor is the last month in that year.
If we run out of years, the predecessor is None.
The algorithm to find the successor is symmetric. See
Section 21.6, “YearCollection.__findPrev(): Find
predecessor month” and Section 21.7, “YearCollection.findNext(): Find
successor month”.
#-- 1 --
# [ if yyyy_mm has a predecessor in self ->
# prev := that predecessor's 'yyyy-mm' month key
# else ->
# prev := None ]
prev = self.__findPrev ( yyyy_mm )
#-- 2 --
# [ if yyyy_mm has a successor in self ->
# next := that successor's 'yyyy-mm' month key
# else ->
# next := None ]
next = self.__findNext ( yyyy_mm )
#-- 3 --
return (prev, next )
For the algorithm, see Section 21.5, “YearCollection.neighbors(): Find
previous and next month”.
# - - - Y e a r C o l l e c t i o n . _ _ f i n d P r e v
def __findPrev ( self, yyyy_mm ):
'''Find the month preceding yyyy_mm.
[ yyyy_mm is a month in self with key string 'yyyy-mm' ->
if self has any months before yyyy_mm ->
return the month key of the immediate predecessor
else -> return None ]
'''
First we find the YearRow containing yyyy_mm, and ask that instance if the month has
a predecessor in the same year.
#-- 1 --
# [ yyyy := year part of yyyy_mm
# mm := month part of yyyy_mm ]
yyyy, mm = yyyy_mm.split('-')
#-- 2 --
# [ yyyy is a year in self ->
# yearRow := YearRow instance for year yyyy ]
yearRow = self[yyyy]
#-- 3 --
# [ if yearRow has a predecessor to month mm ->
# return that month's key as a string 'mm'
# else -> I ]
try:
prev = yearRow.predecessor ( mm )
return '%s-%s' % (yyyy, prev)
except KeyError:
pass
If there is a predecessor, it must be in a previous year.
First we form a list of the year numbers in descending
order. Then we find the position of yyyy
in that list, and step through the remaining elements (if
any) until we find a year that has at least one month in
it, and return the last month.
#-- 4 --
# [ yyyyList := all year key strings in self, sorted
# in descending order ]
yyyyList = self.__yearMap.keys()
yyyyList.sort()
yyyyList.reverse()
#-- 5 --
# [ yyyy is an element of yyyyList ->
# pos := yyyy's position in yyyyList ]
pos = yyyyList.index ( yyyy )
For the method that looks for the last month of a year,
see Section 22.5, “YearRow.lastMonth(): Return the
last month”.
#-- 6 --
# [ if any year with a key in yyyyList[pos+1:] has at
# least one month in it ->
# return the yyyy-mm key of the last month in the
# first such year
# else -> I ]
for prevYear in yyyyList[pos+1:]:
prevRow = self[prevYear]
if len(prevRow) > 0:
lastMM = prevRow.lastMonth()
return '%s-%s' % (prevYear, lastMM)
#-- 7 --
return None
This method is the mirror image of Section 21.6, “YearCollection.__findPrev(): Find
predecessor month”. The algorithm is
discussed in Section 21.5, “YearCollection.neighbors(): Find
previous and next month”.
# - - - Y e a r C o l l e c t i o n . _ _ f i n d N e x t
def __findNext ( self, yyyy_mm ):
'''Find the next month chronologically after yyyy_mm.
[ yyyy_mm is a month in self with key string 'yyyy-mm' ->
if self has any months after yyyy_mm ->
return the month key of the immediate successor
else -> return None ]
'''
#-- 1 --
# [ yyyy := year part of yyyy_mm
# mm := month part of yyyy_mm ]
yyyy, mm = yyyy_mm.split('-')
#-- 2 --
# [ yyyy is a year in self ->
# yearRow := YearRow instance for year yyyy ]
yearRow = self[yyyy]
#-- 3 --
# [ if yearRow has a successor to month mm ->
# return that month's key as a string 'mm'
# else -> I ]
try:
next = yearRow.successor ( mm )
return '%s-%s' % (yyyy, next)
except KeyError:
pass
#-- 4 --
# [ yyyyList := all year key strings in self, sorted
# in ascending order ]
yyyyList = self.__yearMap.keys()
yyyyList.sort()
#-- 5 --
# [ yyyy is an element of yyyyList ->
# pos := yyyy's position in yyyyList ]
pos = yyyyList.index ( yyyy )
For the function that returns the first month of a given
year, see Section 22.4, “YearRow.firstMonth(): Return the
first month”.
#-- 6 --
# [ if any year with a key in yyyyList[pos+1:] has at
# least one month in it ->
# return the yyyy-mm key of the first month in
# the first such year
# else -> I ]
for nextYear in yyyyList[pos+1:]:
nextRow = self[nextYear]
if len(nextRow) > 0:
firstMM = nextRow.firstMonth()
return '%s-%s' % (nextYear, firstMM)
#-- 7 --
return None