'Run through algorithm to solve for maximum free impressions.
Choose how to allocate inventory to each campaign by first mapping out
the distance of each target from the targets we\'re trying to maximize
inventory of, and then assigning inventory to each campaign
preferring to choose the targets that are farthest away.'

def get_free_impressions(self):

    campaigns_by_target = defaultdict(list)
    for campaign in self.campaigns:
        for target_name in campaign.target_names:
            campaigns_by_target[target_name].append(campaign)
    level = 0
    level_by_target_name = {}
    next_level_target_names = set(self.priority_target_names)
    while next_level_target_names:
        target_names = next_level_target_names
        for target_name in target_names:
            level_by_target_name[target_name] = level
        campaigns = chain.from_iterable((campaigns_by_target[target_name] for target_name in target_names))
        next_level_target_names = {target_name for campaign in campaigns for target_name in campaign.target_names if (target_name not in level_by_target_name)}
        level += 1
    for target in self.targets:
        if (target.name not in level_by_target_name):
            level_by_target_name[target.name] = level
    target_names_by_level = defaultdict(list)
    for (target_name, level) in level_by_target_name.iteritems():
        target_names_by_level[level].append(target_name)
    unassigned_by_campaign = {campaign.name: campaign.impressions for campaign in self.campaigns}
    impressions_by_target = {target.name: target.impressions for target in self.targets}
    for level in sorted(target_names_by_level.iterkeys(), reverse=True):
        target_names = target_names_by_level[level]
        campaigns = chain.from_iterable((campaigns_by_target[target_name] for target_name in target_names))
        def sort_val(campaign):
            val = sum(((level_by_target_name[name] + 1) for name in campaign.target_names if (level_by_target_name[name] <= level)))
            return val
        for campaign in sorted(campaigns, key=sort_val):
            campaign_targets = [name for name in target_names if (name in campaign.target_names)]
            for target_name in campaign_targets:
                unassigned = unassigned_by_campaign[campaign.name]
                if (unassigned > 0):
                    available = max(0, impressions_by_target[target_name])
                    assigned = min(unassigned, available)
                    unassigned_by_campaign[campaign.name] -= assigned
                    impressions_by_target[target_name] -= assigned
    penalty = 0
    for campaign in self.campaigns:
        unassigned = unassigned_by_campaign[campaign.name]
        if (unassigned > 0):
            if (not campaign.target_names):
                continue
            target_name = min(campaign.target_names, key=(lambda name: level_by_target_name[name]))
            unassigned_by_campaign[campaign.name] -= unassigned
            impressions_by_target[target_name] -= unassigned
            penalty += unassigned
    free_impressions = sum((impressions_by_target[target_name] for target_name in self.priority_target_names))
    return (free_impressions - penalty)
