2016-07-25 13 views
4

私は{'ip1:port1' : <value>, 'ip1:port2' : <value>, 'ip2:port1' : <value>, ...}という形のpython辞書を持っています。ディクショナリキーは、IP:ポートのペアで構成される文字列です。値はこのタスクにとって重要ではありません。辞書キーのサブセット

私は一意のIPアドレスを持つip:portの組み合わせのリストが必要です。ポートは元のキーに表示されるポートのいずれかにすることができます。上記の例では、['ip1:port1', ip2:port1']['ip1:port2', ip2:port1']の2種類が許容されます。

これを実行するにあたって最も厄介な方法は何ですか?

現在、私の解決策は、それが追加のリストを作成し、それらを捨てるので、私は、それを好きではない

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

"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はdictではなくキーのリストを要求したことに注意してください。私はgroupbyが大好きですが、O(nlogn)ソートを避けるため、私はあなたの第2の解決策を好んでいます。 –

+0

@ PM2Ring Rightしかし、これで最後のステップが簡単になります。私は、 '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

これは、二次的な複雑さを有することに注意してください。すなわち、一意のIPのそれぞれについて「次の」一致エントリを見つけるためにはO(n²)です。また、例えば、IP「1.1.1.1」や「1.1.1.11」を持っている場合、 'startswith'は失敗します。 –

+0

@tobias_k、なぜ複雑さが二次的であるのか理解できません。外側のループが 'set'要素を超えています...内部ループがすべてのキーを反復し、その反復が完了した後にのみジェネレータを作成することを意味しますか? – wl2776

+0

kが一意のIPアドレスの数であり、nがdictのエントリの数である場合、複雑さはk * nであり、これは厳密に二次的ではない(少なくともk << nの場合)必要以上に –

4

これを行う1つの方法は、等価性テストを行うときに文字列のIP部分だけを見るカスタムクラスにキーを変換することです。また、適切な__hash__メソッドを提供する必要があります。

ここでのロジックは、比較のポート部分を無視して、同じIPを持つキーを「参照」するため、そのIPを持つキーがすでに存在する場合にキーをセットに追加しないようにすることですセット内に存在する。ここで

は、後でのPython 2.7以上を実行している場合、機能が設定された理解を代わりに使用することができます

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やPython 3上で動作するいくつかのコードです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())