class Registry:
    '''A registry of runners in a 5K race.  Each runner is identified by their
    email address and is registered in a speed category.
    '''
    
    # The names of the speed categories for a race.
    CATEGORIES = ['<20', '<30', '<40', '>=40']

    # groups: dict{str: list[string]}
    #    Maps a speed category to a list of the email addresses of runners
    #    in that category

    def __init__(self):
        ''' (Registry) -> NoneType

        Initialize a new race registry with no runners entered.
        '''
        self.categories = Registry.CATEGORIES
        self.groups = {}
        for c in self.categories:
            self.groups[c] = []

    def registered_in(self, e):
        ''' (Registry, str) -> str

        Return the category that the runner with email address e is registered
        in, or the empty string if no one with that email address is registered.

        >>> r = Registry()
        >>> r.register('Diane', '<20')
        >>> r.registered_in('Phantom')
        ''
        >>> r.registered_in('Diane') 
        '<20'
        >>>
        '''
        for c in self.categories:
            if e in self.groups[c]:
                return c
        return ''
        
    def register(self, e, c):
        ''' (Registry, str, str) -> NoneType
        
        Register a runner with email address e and category c.  If they had
        previously registered, remove them from their old category and register
        them in category c.  c must occur in CATEGORIES.
        '''
        old_category = self.registered_in(e)
        if old_category:
            self.groups[old_category].remove(e)
        self.groups[c].append(e)

    def category_roster(self, c):
        ''' (Registry, str) -> [str]

        Return a list of the email addresses of all the runners registered in
        category c.  c must occur in CATEGORIES.

        >>> r = Registry()
        >>> r.register('a', '<20')
        >>> r.register('b', '<20')
        >>> r.register('c', '<40')
        >>> r.category_roster('<20')
        ['a', 'b']
        >>> r.category_roster('<30')
        []
        '''
        return self.groups[c]
    
    def __str__(self):
        ''' (Registry) -> str

        Return a str describing this Registry, suitable for a user to read.
        '''
        '''
        This doctest sometimes fails due to the unpredictable order of 
        dictionary entries.  Omit doctest in those cases?  XXX
        >>> r = Registry()
        >>> r.register("a", "<20")
        >>> r.register("b", "<20")
        >>> r.register("c", "<40")
        >>> print(r)
        Runners with speed category <30:
            None
        Runners with speed category <20:
            a
            b
        Runners with speed category <40:
            c
        Runners with speed category >=40:
            None
        '''
        answer = ""
        for (speed, runners) in self.groups.items():
            answer += 'Runners with speed category %s:\n' % speed
            if runners:
                for r in runners:
                    answer += '    %s\n' % r
                # Remove the final newline character.
                #answer = answer[:-1]
            else:
                answer += '    None\n'
        return answer
    
if __name__ == '__main__':
    harry_rosen = Registry()
    harry_rosen.register('gerhard', '<40')
    harry_rosen.register('margot', '<30')
    harry_rosen.register('tom', '<30')
    harry_rosen.register('toni', '<20')
    harry_rosen.register('gerhard', '<30')
    print(harry_rosen)
    print(harry_rosen.category_roster('<30'))

    # Students aren't expected (yet) to run doctest tests, but this is
    # how to do it.
    import doctest
    doctest.testmod()
