简体   繁体   English

Python:在列表中查找所有加性逆对并将其删除

[英]Python: Find all additive inverse pairs in a list and remove them

I have a few lists that contain additive inverse pairs (adding both pairs give you zero), and for each list I am trying to remove those pairs.我有一些包含加性逆对的列表(添加两个对会给你零),对于每个列表,我试图删除这些对。

Below are a few examples, where I am trying to go from this:下面是一些例子,我试图从这里开始:

lst1 = [326, -326, 336, -336, 336]
lst2 = [-35, 35, 838, -838, 440]
lst3 = [544, -544, 544]
lst4 = [-25, 25, 32, -32, -32, 56, 79]

To this:对此:

lst1 = [336]
lst2 = [440]
lst3 = [544]
lst4 = [-32, 56, 79]

You can see that in each list, the additive inverse pairs are removed, leaving the values that have no additive inverse, or the 1 extra non-mirrored value.您可以看到,在每个列表中,加法逆对被删除,留下没有加逆的值,或 1 个额外的非镜像值。

I wish I could share what I have tried, but I have been at it for a few days and any logic I come up with falls apart with the example in lst4 .我希望我能分享我尝试过的东西,但我已经研究了几天,我想出的任何逻辑都与lst4的示例lst4

I seem to have found a solution that yields exactly what I need, however it is written in R which I have no experience in, so I cannot understand the logic.我似乎找到了一个解决方案,它产生了我所需要的东西,但是它是用 R 编写的,我没有经验,所以我无法理解逻辑。

Any ideas on how I can overcome this?关于如何克服这个问题的任何想法?

You can use defaultdict to keep track of additive pairs as well as handle duplicates:您可以使用defaultdict来跟踪加法对以及处理重复项:

from collections import defaultdict
from itertools import chain
def remove_additive_pairs(sequence: list) -> list:
    """ Remove additive pairs from given sequence. """
    result = defaultdict(list)
    
    for element in sequence:
        if -element in result:
            del result[-element]
        else:
            result[element].append(element)
            
    return list(chain.from_iterable(result.values()))

>>> lst5 = [-25, 25, 32, -25, 25, 32]
>>> for seq in [lst1, lst2, lst3, lst4, lst5]:
        print(remove_additive_pairs(seq))

[336]
[440]
[544]
[-32, 56, 79]
[32, 32]

Of course you can do the same without importing itertools.chain or collections.defaultdict :当然,您可以在不导入itertools.chaincollections.defaultdict情况下执行相同操作:

def chain(args):
    for lst in args:
        for elem in lst:
            yield elem

            
def remove_additive_pairs(sequence: list) -> list:
    """ Remove additive pairs from given sequence. """
    result = dict()
    
    for element in sequence:
        if -element in result:
            del result[-element]
        else:
            result.setdefault(element, []).append(element)
            
    return list(chain(result.values()))

defaultdict is a dict-like container that can provide default values for missing keys . defaultdict是一个dict-like容器,可以为missing keys提供default值。 For example:例如:

>>> d = defaultdict(list)
>>> d[5]
[]

So defining defaultdict(list) ensures that when we get a number whose inverse is not already in the keys, we can add that number as a key to the dictionary, as well as append it to a list in its values in one step.因此,定义defaultdict(list)可确保当我们得到一个数字,其倒数不在键中时,我们可以将该数字作为key添加到字典中,并一步appendappendlist中的值中。

First, count how many times each value appears in the list.首先,计算每个值在列表中出现的次数。 Then iterate through the list, either populating a new list or throwing the value away and decrementing the counter of the inverse value.然后遍历列表,要么填充新列表,要么丢弃该值并递减反向值的计数器。

from collections import Counter

def remove_inverses(lst):
  value_counts = Counter(lst)
  result = []
  for element in lst:
    if value_counts.get(-element):
      value_counts[-element] -= 1
    else:
      result.append(element)
  return result

You could keep track of a running total of the elements you've seen in a (default)dictionary documentation您可以跟踪您在(默认)字典文档中看到的所有元素

A dictionary is a data structure that holds key-value pairs and allows quick lookup.字典是一种保存键值对并允许快速查找的数据结构。 In this instance, the elements of our input list will be our keys, and the values will be a count.在这种情况下,我们输入列表的元素将是我们的键,而值将是一个计数。 You access values in a dictionary like so: counter[key] = value .您可以像这样访问字典中的值: counter[key] = value

A default dictionary simply makes it so that when key is not already a part of the dictionary, it automatically generates a default value for it instead of throwing a KeyError .默认字典只是让它在key还不是字典的一部分时,它会自动为它生成一个默认值,而不是抛出KeyError So we define counter = collections.defaultdict(int) .所以我们定义了counter = collections.defaultdict(int) The int specifies the default factory function for the values of yet-undefined keys. int为尚未定义的键的值指定默认工厂函数。

Every time you see an unpaired number, you increment the counter for that number.每次看到未配对的数字时,都会增加该数字的计数器。 Every time you find a number that has a pair in the counter, you decrement the counter for the pair.每次您在计数器中找到一个有一对的数字时,您就减少该对的计数器。 At the end, the keys of the counter will be the elements of your list, and the values will be the number of unpaired instances of that element were found, so we translate that into a list as required.最后,计数器的键将是列表的元素,值将是找到的该元素的未配对实例的数量,因此我们根据需要将其转换为列表。 Note that this will not preserve the order of the elements in the original list.请注意,这不会保留原始列表中元素的顺序。

import collections

def filterInverse(lst):
    counter = collections.defaultdict(int)
    for num in lst:
        inverse = -num
        if counter[inverse] > 0:
            # Inverse was found previously in the counter
            # Decrement counter[inverse]
            counter[inverse] -= 1
            # And do nothing else with this number
        else: # Inverse wasn't found in the counter
            # This is an unpaired number so add it to the counter
            counter[num] += 1
    # Finally, go over the dict and return the keys whose values are nonzero
    # Repeat each item by its count
    # print(counter)
    rv = []
    for num, ct in counter.items():
        for _ in range(ct):
            rv.append(num)
    return rv

Running this with the inputs in your question and that we discussed in the comments,使用您问题中的输入以及我们在评论中讨论的内容运行此程序,

>>> filterInverse(lst1)
Out: [336]

>>> filterInverse(lst2)
Out: [440]

>>> filterInverse(lst3)
Out: [544]

>>> filterInverse(lst4)
Out: [-32, 56, 79]

>>> lst5 = [-1, 0, 1, -1, -1]
>>> filterInverse(lst5)
Out: [-1, -1, 0]

If you're dead set against using defaultdict , you can get the same behavior by using a regular dict.get(key, default) documentation :如果您坚决反对使用defaultdict ,您可以通过使用常规dict.get(key, default) 文档获得相同的行为:

counter = dict()

...
    ...
        if counter.get(inverse, 0) > 0:
            # Inverse was found previously in the counter
            # Decrement counter[inverse]
            counter[inverse] = counter.get(inverse, 0) - 1
            # And do nothing else with this number
        else: # Inverse wasn't found in the counter
            # This is an unpaired number so add it to the counter
            counter[num] = counter.get(num, 0) + 1
    ...
...

Using the awesome collections.Counter :使用很棒的collections.Counter

from collections import Counter
from itertools import repeat

def remove_opposite_pairs(seq):
    dplus = Counter(seq)
    dminus = Counter(-x for x in seq)
    for x,n in (dplus - dminus).items():
        yield from repeat(x, n)

lsts = [[326, -326, 336, -336, 336],
        [-35, 35, 838, -838, 440],
        [544, -544, 544],
        [-25, 25, 32, -32, -32, 56, 79],
        [-1, 0, 1, -1, -1]]

for l in lsts:
    print(list(remove_opposite_pairs(l)))
# [336]
# [440]
# [544]
# [-32, 56, 79]
# [-1, -1]

Note the weird behaviour on 0, because 0 is its own opposite.注意 0 上的奇怪行为,因为 0 是它自己的对立面。 I could fix it, but first you need to edit your question to specify exactly how to handle 0.我可以解决它,但首先您需要编辑您的问题以准确指定如何处理 0。

Here is a possible fix:这是一个可能的修复方法:

from collections import Counter
from itertools import repeat

def remove_opposite_pairs(seq):
    dplus = Counter(seq)
    dminus = Counter(-x for x in seq)
    for x,n in (dplus - dminus).items():
        yield from repeat(x, n)
    if dplus[0] % 2 == 1:
        yield 0

lsts = [[-1, 0, 1, -1, -1],
        [0, 0],
        [0, 0, 0]]

for l in lsts:
    print(list(remove_opposite_pairs(l)))
# [-1, -1, 0]
# []
# [0]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM