using defaultdict to clean up implementation