2016-07-21 9 views
1

Ich habe eine Reihe von Objekten, und ich möchte Werte der Menge mit einem Lambda-Ausdruck oder eine Art von Bedingung zu entfernen.Pop einen Wert aus einer Menge mit einer Bedingung

ich dies so tun würde:

def pop(container, cond): 
    value = None 
    removed = False 

    for x in container: 
     if cond(x): 
      value = x 
      removed = True 
      container.remove(x) 
      break 

    if not removed: 
     raise Exception('No value to pop') 

    return value 

Ein Beispiel usecase ist:

compare_object = {"price": 100, "quantity": 1, "product_id": 2} 
objs = set([....]) 

def comparison(obj): 
    def wrap(obj2): 
     return (
      obj2['price'] == obj['price'] and 
      obj2['quantity'] == obj['quantity'] and 
      obj2['product_id'] == obj['product_id'] 
     ) 


similar_obj = pop(objs, comparison(compare_object)) 

Auf diese Weise haben wir ein ähnliches Objekt aus einer Menge erhalten und folglich wird die Menge reduziert werden nach einem Aufruf der Methode.

Ich würde gerne wissen, ob es eine einfachere Möglichkeit gibt, dies mit einer tatsächlichen Datenstruktur zu tun, die bereits in Python verfügbar ist, anstatt das Set zu durchlaufen, was bei großen Sets teuer sein könnte.

+0

Ok ... Also, was ist das Problem? Gibt es einen Fehler? – mgilson

+0

Nein, ich würde gerne wissen, ob ich das Rad neu erfinde oder ob es eine Datenstruktur gibt, die das schon tut. –

+0

Ich glaube nicht, dass es ein eingebautes Set-ähnliches Objekt gibt, das dies tut. 'set' _does_ haben eine' .pop'-Methode, aber es gibt Ihnen keine Kontrolle darüber, welches Element entfernt wird ... – mgilson

Antwort

2

Statt Ihre komplizierte for Schleife, könnten Sie eine filter von diesem Zustand (oder itertools.ifilter für Python 2) erstellen und entfernen Sie das next Element aus diesem Filter.

def pop(container, cond): 
    try: 
     value = next(filter(cond, container)) 
     container.remove(value) 
     return value 
    except StopIteration: 
     raise Exception("No Value to pop") 

Beispiel:

>>> lst = [1,2,3,4,5] 
>>> cond = lambda x: x % 2 == 0 
>>> pop(lst, cond) 
2 
>>> pop(lst, cond) 
4 
>>> pop(lst, cond) 
Exception: No Value to pop 
>>> lst 
[1, 3, 5] 

Oder Sie einfach Ihre Funktion sofort Rückgabe des gefundenen Wert vereinfachen könnte:

def pop(container, cond): 
    for x in container: 
     if cond(x): 
      container.remove(x) 
      return x 
    raise Exception('No value to pop') 
+0

Ich mag das, der Filter ist viel einfacher und da es einen Iterator zurückgibt, denke ich, es wird nicht die ganze Liste scannen. –

+0

Es endete damit, es mit einem defaultdict zu tun, da meine Bedingung als Schlüssel eines Diktats der Liste generiert werden kann. –

1

Ihre pop() Methode ist im Grunde ein Duplikat der bereits eingebauten filter() in Kombination mit dem Aufruf next() auf dem resultierenden filter Objekt.

compare_object = {"price": 100, "quantity": 1, "product_id": 2} 
objs = set([....]) 

def comparison(obj): 
    def wrap(obj2): 
     return (
      obj2['price'] == obj['price'] and 
      obj2['quantity'] == obj['quantity'] and 
      obj2['product_id'] == obj['product_id'] 
     ) 

similar_obj = next(filter(comparison(compare_object), objs)) 

Diese StopIteration erhöhen wird, wenn es keine Übereinstimmung ist: Sie können Ihren Code dies vereinfachen.

Zur Leistung: Wenn Sie eine Menge haben, die definitionsgemäß ungeordnet ist, und Ihre Bedingung für jeden Eintrag überprüft werden muss, gibt es keine Möglichkeit, über alle Elemente zu iterieren. Aber da Sie nur das erste Vorkommen abrufen, wird es praktisch nie notwendig sein, über das ganze Set zu iterieren. Da filter() seine Ergebnisse träge auswertet, verhält es sich genauso und durchläuft nur die Menge, bis es die erste Übereinstimmung findet.

+0

Aber das "Pop" das Element aus der Sammlung, nicht wahr? Der ursprüngliche Code von OP wird ebenfalls gestoppt, sobald die erste Übereinstimmung mit 'break' gefunden wird. –

+0

Richtig, tut es nicht. Aber da das Entfernen von Elementen aus einer Menge "O (1)" ist, kann dies durch einen Aufruf von "objs.remove (ashnic_obj)" ohne viel zusätzliche Kosten getan werden. – Felk

+0

Meine Version stoppt auch beim ersten Vorkommen, weil 'next()' nur einmal auf dem 'filter'-Objekt aufgerufen wird, das träge auswertet – Felk

0

Aus verschiedenen Gründen entschied ich mich für eine defaultdict Variante. Anstatt eine set zu verwenden. Ich speichere nur die Daten in einem Standarddict mit einer Liste.

Einrichtung der Behälter:

container = defaultdict(list) 
for exp_line in exp_lines: 
    container[(
     exp_line.unit_amount, 
     exp_line.unit_quantity, 
     exp_line.product_id, 
     exp_line.uom_id 
    )].append(exp_line) 

Pop aus dem Behälter:

obj = container[(
    -(aal.amount/aal.unit_amount), 
    aal.unit_amount, 
    aal.product_id, 
    aal.product_uom_id 
)].pop() 

Der einzige Nachteil ich mit dieser Methode sehen ist, dass es keine einfache Möglichkeit, wenn der Behälter leer zu wissen ist, ist oder nicht. Wenn Sie jedoch versuchen, aus einer leeren Liste zu springen, wird eine Ausnahme wie erwartet ausgelöst, und die Daten werden tatsächlich aus dem Container entfernt.

Da die Bedingung ziemlich "behoben" ist, als ob ich eine einzige Bedingung für das Set verwenden würde, ist es viel einfacher so. Aber wenn ich "benutzerdefinierte" Schlüssel verwenden müsste, die nicht gehashed werden können.Die anderen Antworten mit "next/filter" wären ein besseres Werkzeug, denke ich.

Kein Algorithmus dort wirklich.