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.
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
.
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.
Any ideas on how I can overcome this?
CodePudding user response:
You can use defaultdict
to keep track of additive pairs as well as handle duplicates:
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
:
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
.
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.
CodePudding user response:
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
CodePudding user response:
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
.
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
. So we define counter = collections.defaultdict(int)
. The int
specifies the default factory function for the values of yet-undefined keys.
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:
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
...
...