Trek twee lijsten in Python

In Python, hoe kan men twee niet-unieke, ongeordende lijsten aftrekken? Zeg dat we a = [0,1,2,1,0]en b = [0, 1, 1]Ik wil graag iets als c = a - ben hebben cBE [2, 0]of [0, 2]BESTELLINGEN NIET het is belangrijk voor mij. Dit zou een uitzondering moeten gooien als een niet alle elementen in b.

Opmerking Dit is anders dan sets! Ik ben niet geïnteresseerd in het vinden van het verschil van de sets elementen in A en B, ik ben geïnteresseerd in het verschil tussen de daadwerkelijke collecties van elementen in A en b.

Ik kan dit doen met een voor lus, opkijken van het eerste element van B in A en vervolgens het element uit B en van A, enz. Maar dit doet me niet aan, het zou heel inefficiënt zijn (bestelling van O(n^2)TIJD) Hoewel het geen probleem zou moeten zijn om dit te doen in O(n log n)tijd.


Antwoord 1, Autoriteit 100%

Python 2.7 en 3.2 Toegevoegd collections.Counterklasse, die een woordenboek-subklasse is dat elementen op het aantal voorkomen van het element kaarten. Dit kan worden gebruikt als een multiset. Je kunt zoiets doen:

from collections import Counter
a = Counter([0, 1, 2, 1, 0])
b = Counter([0, 1, 1])
c = a - b  # ignores items in b missing in a
print(list(c.elements()))  # -> [0, 2]

Ook, als u wilt controleren of elk element in bin ais:

# a[key] returns 0 if key not in a, instead of raising an exception
assert all(a[key] >= b[key] for key in b)

Maar aangezien u vastzit met 2.5, kunt u proberen het te importeren en uw eigen versie te definiëren als dat mislukt. Op die manier zult u zeker zijn dat u de nieuwste versie krijgt als deze beschikbaar is en terugvalt naar een werkversie zo niet. U zult ook profiteren van snelheidsverbeteringen als het in de toekomst wordt geconverteerd naar een C-implementatie.

try:
   from collections import Counter
except ImportError:
    class Counter(dict):
       ...

U kunt de huidige Python-bron hier .


Antwoord 2, Autoriteit 197%

Ik weet het “voor” is niet wat u wilt, maar het is eenvoudig en duidelijk:

for x in b:
  a.remove(x)

of als leden van bmogelijk niet in azijn. Gebruik dan:

for x in b:
  if x in a:
    a.remove(x)

Antwoord 3, Autoriteit 106%

ik zou het op een eenvoudiger manier doen:

a_b = [e for e in a if not e in b ]

.. Zoals die schreef, is dit verkeerd – het werkt alleen als de items uniek zijn in de lijsten. En als ze dat zijn, is het beter om

te gebruiken

a_b = list(set(a) - set(b))

Antwoord 4, Autoriteit 19%

Ik weet niet zeker wat het bezwaar tegen een voor lus is: er is geen multiset in Python, dus u kunt geen ingebouwde container gebruiken om u te helpen.

lijkt mij alles op één regel (indien mogelijk) waarschijnlijk wordachtig complex te begrijpen. Ga voor leesbaarheid en kus. Python is niet C:)


Antwoord 5, Autoriteit 16%

Python 2.7+ en 3.0 hebben collections.counter ( aka multiset). De documentatie Links naar recept 576611: tellerklasse voor Python 2.5:

from operator import itemgetter
from heapq import nlargest
from itertools import repeat, ifilter
class Counter(dict):
    '''Dict subclass for counting hashable objects.  Sometimes called a bag
    or multiset.  Elements are stored as dictionary keys and their counts
    are stored as dictionary values.
    >>> Counter('zyzygy')
    Counter({'y': 3, 'z': 2, 'g': 1})
    '''
    def __init__(self, iterable=None, **kwds):
        '''Create a new, empty Counter object.  And if given, count elements
        from an input iterable.  Or, initialize the count from another mapping
        of elements to their counts.
        >>> c = Counter()                           # a new, empty counter
        >>> c = Counter('gallahad')                 # a new counter from an iterable
        >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
        >>> c = Counter(a=4, b=2)                   # a new counter from keyword args
        '''        
        self.update(iterable, **kwds)
    def __missing__(self, key):
        return 0
    def most_common(self, n=None):
        '''List the n most common elements and their counts from the most
        common to the least.  If n is None, then list all element counts.
        >>> Counter('abracadabra').most_common(3)
        [('a', 5), ('r', 2), ('b', 2)]
        '''        
        if n is None:
            return sorted(self.iteritems(), key=itemgetter(1), reverse=True)
        return nlargest(n, self.iteritems(), key=itemgetter(1))
    def elements(self):
        '''Iterator over elements repeating each as many times as its count.
        >>> c = Counter('ABCABC')
        >>> sorted(c.elements())
        ['A', 'A', 'B', 'B', 'C', 'C']
        If an element's count has been set to zero or is a negative number,
        elements() will ignore it.
        '''
        for elem, count in self.iteritems():
            for _ in repeat(None, count):
                yield elem
    # Override dict methods where the meaning changes for Counter objects.
    @classmethod
    def fromkeys(cls, iterable, v=None):
        raise NotImplementedError(
            'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')
    def update(self, iterable=None, **kwds):
        '''Like dict.update() but add counts instead of replacing them.
        Source can be an iterable, a dictionary, or another Counter instance.
        >>> c = Counter('which')
        >>> c.update('witch')           # add elements from another iterable
        >>> d = Counter('watch')
        >>> c.update(d)                 # add elements from another counter
        >>> c['h']                      # four 'h' in which, witch, and watch
        4
        '''        
        if iterable is not None:
            if hasattr(iterable, 'iteritems'):
                if self:
                    self_get = self.get
                    for elem, count in iterable.iteritems():
                        self[elem] = self_get(elem, 0) + count
                else:
                    dict.update(self, iterable) # fast path when counter is empty
            else:
                self_get = self.get
                for elem in iterable:
                    self[elem] = self_get(elem, 0) + 1
        if kwds:
            self.update(kwds)
    def copy(self):
        'Like dict.copy() but returns a Counter instance instead of a dict.'
        return Counter(self)
    def __delitem__(self, elem):
        'Like dict.__delitem__() but does not raise KeyError for missing values.'
        if elem in self:
            dict.__delitem__(self, elem)
    def __repr__(self):
        if not self:
            return '%s()' % self.__class__.__name__
        items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
        return '%s({%s})' % (self.__class__.__name__, items)
    # Multiset-style mathematical operations discussed in:
    #       Knuth TAOCP Volume II section 4.6.3 exercise 19
    #       and at http://en.wikipedia.org/wiki/Multiset
    #
    # Outputs guaranteed to only include positive counts.
    #
    # To strip negative and zero counts, add-in an empty counter:
    #       c += Counter()
    def __add__(self, other):
        '''Add counts from two counters.
        >>> Counter('abbb') + Counter('bcc')
        Counter({'b': 4, 'c': 2, 'a': 1})
        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem in set(self) | set(other):
            newcount = self[elem] + other[elem]
            if newcount > 0:
                result[elem] = newcount
        return result
    def __sub__(self, other):
        ''' Subtract count, but keep only results with positive counts.
        >>> Counter('abbbc') - Counter('bccd')
        Counter({'b': 2, 'a': 1})
        '''
        if not isinstance(other, Counter):
            return NotImplemented
        result = Counter()
        for elem in set(self) | set(other):
            newcount = self[elem] - other[elem]
            if newcount > 0:
                result[elem] = newcount
        return result
    def __or__(self, other):
        '''Union is the maximum of value in either of the input counters.
        >>> Counter('abbb') | Counter('bcc')
        Counter({'b': 3, 'c': 2, 'a': 1})
        '''
        if not isinstance(other, Counter):
            return NotImplemented
        _max = max
        result = Counter()
        for elem in set(self) | set(other):
            newcount = _max(self[elem], other[elem])
            if newcount > 0:
                result[elem] = newcount
        return result
    def __and__(self, other):
        ''' Intersection is the minimum of corresponding counts.
        >>> Counter('abbb') & Counter('bcc')
        Counter({'b': 1})
        '''
        if not isinstance(other, Counter):
            return NotImplemented
        _min = min
        result = Counter()
        if len(self) < len(other):
            self, other = other, self
        for elem in ifilter(self.__contains__, other):
            newcount = _min(self[elem], other[elem])
            if newcount > 0:
                result[elem] = newcount
        return result
if __name__ == '__main__':
    import doctest
    print doctest.testmod()

dan kunt u schrijven

a = Counter([0,1,2,1,0])
 b = Counter([0, 1, 1])
 c = a - b
 print list(c.elements())  # [0, 2]

Antwoord 6, Autoriteit 12%

Lijstbegrip gebruiken:

[i for i in a if not i in b or b.remove(i)]

zou de truc doen. Het zou echter in het proces veranderen. Maar ik ben het eens met JKP en Dyno Fu dat het gebruik van een voor lus beter zou zijn.

Misschien kan iemand een beter voorbeeld maken dat een lijst bevat, maar nog steeds kus is?


Antwoord 7, Autoriteit 6%

Om het punt van JKP te bewijzen dat ‘alles op één regel waarschijnlijk wordachtig complex zal zijn om te begrijpen’, creëerde ik een one-liner. Mod u alsjeblieft niet aan omdat ik begrijp dat dit geen oplossing is die je daadwerkelijk zou moeten gebruiken. Het is alleen voor demonische doeleinden.

Het idee is om de waarden in een één voor één toe te voegen, zolang de totale tijden die u hebt toegevoegd, is die waarde kleiner is dan het totale aantal keren deze waarde in een minpuntje is het aantal keren dat het in b is :

[ value for counter,value in enumerate(a) if a.count(value) >= b.count(value) + a[counter:].count(value) ]

de horror! Maar misschien kan iemand het verbeteren? Is het zelfs fout?

EDIT: Seeing Devin Jeanpierre Commentaar over het gebruik van een Woordenboekgegevens, ik bedacht met deze Oneliner:

sum([ [value]*count for value,count in {value:a.count(value)-b.count(value) for value in set(a)}.items() ], [])

beter, maar nog steeds onleesbaar.


Antwoord 8

U kunt zoiets proberen:

class mylist(list):
    def __sub__(self, b):
        result = self[:]
        b = b[:]
        while b:
            try:
                result.remove(b.pop())
            except ValueError:
                raise Exception("Not all elements found during subtraction")
        return result
a = mylist([0, 1, 2, 1, 0] )
b = mylist([0, 1, 1])
>>> a - b
[2, 0]

Je moet echter definiëren wat [1, 2, 3] – [5, 6] moet uitvoeren, ik denk dat je [1, 2, 3] wilt, daarom negeer ik de ValueError.

Bewerken:
Nu zie ik dat je een uitzondering wilde als aniet alle elementen bevat, deze toegevoegd in plaats van de ValueError door te geven.


Antwoord 9

Ik heb geprobeerd een elegantere oplossing te vinden, maar het beste wat ik kon doen was eigenlijk hetzelfde als wat Dyno Fu zei:

from copy import copy
def subtract_lists(a, b):
    """
    >>> a = [0, 1, 2, 1, 0]
    >>> b = [0, 1, 1]
    >>> subtract_lists(a, b)
    [2, 0]
    >>> import random
    >>> size = 10000
    >>> a = [random.randrange(100) for _ in range(size)]
    >>> b = [random.randrange(100) for _ in range(size)]
    >>> c = subtract_lists(a, b)
    >>> assert all((x in a) for x in c)
    """
    a = copy(a)
    for x in b:
        if x in a:
            a.remove(x)
    return a

Antwoord 10

Hier is een relatief lange, maar efficiënte en leesbare oplossing. Het is O(n).

def list_diff(list1, list2):
    counts = {}
    for x in list1:
        try:
            counts[x] += 1
        except:
            counts[x] = 1
    for x in list2:
        try:
            counts[x] -= 1
            if counts[x] < 0:
                raise ValueError('All elements of list2 not in list2')
        except:
            raise ValueError('All elements of list2 not in list1') 
    result = []
    for k, v in counts.iteritems():
        result += v*[k] 
    return result
a = [0, 1, 1, 2, 0]
b = [0, 1, 1]
%timeit list_diff(a, b)
%timeit list_diff(1000*a, 1000*b)
%timeit list_diff(1000000*a, 1000000*b)
100000 loops, best of 3: 4.8 µs per loop
1000 loops, best of 3: 1.18 ms per loop
1 loops, best of 3: 1.21 s per loop

Antwoord 11

U kunt hiervoor de constructie mapgebruiken. Het ziet er redelijk goed uit, maar pas op dat de regel mapzelf een lijst met Noneen retourneert.

a = [1, 2, 3]
b = [2, 3]
map(lambda x:a.remove(x), b)
a

Antwoord 12

c = [i for i in b if i not in a]

Antwoord 13

list(set([x for x in a if x not in b]))
  • Laat aen bongemoeid.
  • Is een unieke set van “a – b”.
  • Gereed.

Other episodes