2016-07-25 55 views
4

我有一个形式为{'ip1:port1' : <value>, 'ip1:port2' : <value>, 'ip2:port1' : <value>, ...}的Python字典。字典密钥是字符串,由ip:port对组成。这个任务的价值观并不重要。字典键的子集

我需要一个带有唯一IP地址的ip:port组合列表,端口可以是任何出现在原始密钥中的组合。例如以上,两种变体是可接受的:['ip1:port1', ip2:port1']['ip1:port2', ip2:port1']

什么是最pythonic这样做呢?

目前我的解决办法是

def get_uniq_worker_ips(workers): 
    wip = set(w.split(':')[0] for w in workers.iterkeys()) 
    return [[worker for worker in workers.iterkeys() if worker.startswith(w)][0] for w in wip] 

我不喜欢它,因为它创造了额外的列表,然后丢弃它们。

+0

然后使用genexs代替。 –

+0

对不起,你能更具体吗? – wl2776

+0

by“genexs”,我认为他的意思是“生成器表达式”,这意味着你创建一个生成器而不是一个列表。这可以通过在列表理解中用方括号'()'改变方括号'[]'来完成。 –

回答

7

您可以通过相同的IP地址使用itertools.groupby到组:

data = {'ip1:port1' : "value1", 'ip1:port2' : "value2", 'ip2:port1' : "value3", 'ip2:port2': "value4"} 
by_ip = {k: list(g) for k, g in itertools.groupby(sorted(data), key=lambda s: s.split(":")[0])} 
by_ip 
# {'ip1': ['ip1:port1', 'ip1:port2'], 'ip2': ['ip2:port1', 'ip2:port2']} 

然后随便挑,从IP地址的不同群体的任何一个。

{v[0]: data[v[0]] for v in by_ip.values()} 
# {'ip1:port1': 'value1', 'ip2:port1': 'value3'} 

或者更短,使得发电机表达式从组仅仅是第一项:

one_by_ip = (next(g) for k, g in itertools.groupby(sorted(data), key=lambda s: s.split(":")[0])) 
{key: data[key] for key in one_by_ip} 
# {'ip1:port1': 'value1', 'ip2:port1': 'value3'} 

但是请注意,groupby需要进行排序的输入数据。所以,如果你想避免排序字典中的所有键,你应该使用已经看过的键的set

seen = set() 
not_seen = lambda x: not(x in seen or seen.add(x)) 
{key: data[key] for key in data if not_seen(key.split(":")[0])} 
# {'ip1:port1': 'value1', 'ip2:port1': 'value3'} 

这是类似的解决方案,但不是循环的唯一钥匙,并找到在字典匹配键为每个,您循环键和检查您是否已经看到了IP。

+0

请注意,OP要求提供一个键列表,而不是一个字典。尽管我喜欢groupby,但我更喜欢你的第二种解决方案,因为它避免了O(nlogn)排序。 –

+0

@ PM2Ring对,但是这会让最后一步更容易。我同意'set'解决方案可能是最好的,需要最少的时间和空间。 'groupby'只是我第一件想到的事情,我不想在收到一些赞扬之后将其删除。 –

+0

够公平的。我当然同意你不应该从已经收到upvotes的答案中删除代码。 –

0

我已经改变了我的解决方案中的几个字符,现在我满意了。

def get_uniq_worker_ips(workers): 
    wip = set(w.split(':')[0] for w in workers.iterkeys()) 
    return [next(worker for worker in workers.iterkeys() if worker.startswith(w)) for w in wip] 

感谢@Ignacio Vazquez-Abrams和@ M.T。为解释。

+2

请注意,这具有二次复杂性,即O(n2)用于找到每个唯一IP的“下一个”匹配条目。另外,如果你有IP 1.1.1.1和1.1.1.11,'startswith'将会失败。 –

+0

@tobias_k,我不明白为什么复杂性是二次的。外层循环超过了'set'元素......你的意思是内层循环会迭代所有的键并在迭代完成后才创建生成器? – wl2776

+0

如果k是唯一IP地址的数量,并且n是词典中条目的数量,那么复杂度是k * n,它被认为不完全是二次的(至少如果k << n),但仍然高得多不必要。 –

4

这样做的一种方法是将您的密钥转换为自定义类,该类只在进行相等性测试时查看字符串的IP部分。它还需要提供适当的方法__hash__

这里的逻辑是set构造函数将“看到”具有相同IP的密钥,忽略比较中的端口部分,所以如果具有该IP的密钥已经是密钥目前在集合中。

下面是一些代码,Python的2或Python 3运行

class IPKey(object): 
    def __init__(self, s): 
     self.key = s 
     self.ip, self.port = s.split(':', 1) 

    def __eq__(self, other): 
     return self.ip == other.ip 

    def __hash__(self): 
     return hash(self.ip) 

    def __repr__(self): 
     return 'IPKey({}:{})'.format(self.ip, self.port) 

def get_uniq_worker_ips(workers): 
    return [k.key for k in set(IPKey(k) for k in workers)] 

# Test 

workers = { 
    'ip1:port1' : "val", 
    'ip1:port2' : "val", 
    'ip2:port1' : "val", 
    'ip2:port2' : "val", 
} 

print(get_uniq_worker_ips(workers))  

输出

['ip2:port1', 'ip1:port1'] 

如果你正在运行的Python 2.7或更高版本,该功能可以使用一组的理解,而不是在构造函数调用set()内的那个生成器表达式。

def get_uniq_worker_ips(workers): 
    return [k.key for k in {IPKey(k) for k in workers}] 

IPKey.__repr__方法也不是绝对必要的,但我想给我的所有类一__repr__,因为它可以在开发过程中得心应手。


这里是一个更简洁的解决方案,它是非常有效的Jon Clements礼貌。它通过字典理解建立所需的列表。

def get_uniq_worker_ips(workers): 
    return list({k.partition(':')[0]:k for k in workers}.values())