Next / Previous / Contents / TCC Help System / NM Tech homepage

21. class YearCollection: The top-level data structure

The single instance of this class holds the entire collection of years and months.

noteweb
# - - - - -   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.

noteweb
      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 ]
    '''

21.1. YearCollection.__init__(): Constructor

All the constructor needs is to set up the invariant on the .__yearMap attribute by making it an empty dictionary.

noteweb
# - - -   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  =  {}

21.2. YearCollection.addYear(): Add a year

This method creates a new YearRow instance and adds it to self. See Section 22, “class YearRow: Container for one year's records”.

noteweb
# - - -   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

21.3. YearCollection.__getitem__(): Return self[yyyy]

This method implements the index ([...]) operation on a YearCollection. If there is no such year, it will raise KeyError.

noteweb
# - - -   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]

21.4. YearCollection.genYearsRev(): Generate years in reverse chronological order

noteweb
# - - -   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.

noteweb
        #-- 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

21.5. YearCollection.neighbors(): Find previous and next month

For a given month key expressed as a 'yyyy-mm' string, this method finds the preceding and following months, and returns a two-element tuple containing the month keys of its immediate neighbors, using None for the predecessor of the first month in the set, or for the successor of the last month.

noteweb
# - - -   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.

  1. If year yyyy has any months before mm, the predecessor is the last such month.

  2. 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.

  3. 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”.

noteweb
        #-- 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 )

21.6. YearCollection.__findPrev(): Find predecessor month

For the algorithm, see Section 21.5, “YearCollection.neighbors(): Find previous and next month”.

noteweb
# - - -   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.

noteweb
        #-- 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.

noteweb
        #-- 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”.

noteweb
        #-- 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

21.7. YearCollection.findNext(): Find successor month

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”.

noteweb
# - - -   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”.

noteweb
        #-- 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