twoSum to find all the possible unique couples - python

I have such a problem
Given an array nums of n integers, are there elements a, b in nums such that a + b = 10? Find all unique couples in the array which gives the sum of target.
Note:
The solution set must not contain duplicate couples.
Example:
Given nums = [4, 7, 6, 3, 5], target = 10
because 4+ 6= 7+ 3 = 10
return [[4, 6], [7,3]]
My solution:
class SolutionAll: #Single Pass Approach
def twoSum(self, nums, target) -> List[List[int]]:
"""
:type nums: List[int]
:type target: int
"""
nums.sort()
nums_d:dict = {}
couples = []
if len(nums) < 2:
return []
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: continue #skip the duplicates
complement = target - nums[i]
if nums_d.get(complement) != None:
couples.append([nums[i], complement])
nums_d[nums[i]] = i
return couples
TestCase Results:
target: 9
nums: [4, 7, 6, 3, 5]
DEBUG complement: 6
DEBUG nums_d: {3: 0}
DEBUG couples: []
DEBUG complement: 5
DEBUG nums_d: {3: 0, 4: 1}
DEBUG couples: []
DEBUG complement: 4
DEBUG nums_d: {3: 0, 4: 1, 5: 2}
DEBUG couples: [[5, 4]]
DEBUG complement: 3
DEBUG nums_d: {3: 0, 4: 1, 5: 2, 6: 3}
DEBUG couples: [[5, 4], [6, 3]]
DEBUG complement: 2
DEBUG nums_d: {3: 0, 4: 1, 5: 2, 6: 3, 7: 4}
DEBUG couples: [[5, 4], [6, 3]]
result: [[5, 4], [6, 3]]
.
target: 2
nums: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
DEBUG complement: 2
DEBUG nums_d: {0: 0}
DEBUG couples: []
DEBUG complement: 1
DEBUG nums_d: {0: 0, 1: 9}
DEBUG couples: []
result: []
The solution works with [4, 7, 6, 3, 5] but failed with [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
I tried to remove the duplicates but get an unexpected results.
How could solve the problem with this one Pass solution?

The problem with your code is that it skips duplicate numbers, not duplicate pairs. Because of the
if i > 0 and nums[i] == nums[i-1]: continue #skip the duplicates
your code never tries to sum 1 + 1 = 2.
Here's a working solution with O(n) complexity:
from collections import Counter
def two_sum(nums, target):
nums = Counter(nums) # count how many times each number occurs
for num in list(nums): # iterate over a copy because we'll delete elements
complement = target - num
if complement not in nums:
continue
# if the number is its own complement, check if it
# occurred at least twice in the input
if num == complement and nums[num] < 2:
continue
yield (num, complement)
# delete the number from the dict so that we won't
# output any duplicate pairs
del nums[num]
>>> list(two_sum([4, 7, 6, 3, 5], 10))
[(4, 6), (7, 3)]
>>> list(two_sum([0, 0, 0, 1, 1, 1], 2))
[(1, 1)]
See also:
collections.Counter
What does the "yield" keyword do?

Not sure what's wrong with your solution (and not sure what's right with it either), but you can achieve this easily in a "pretty-pythonic" manner:
def func(nums,target):
return [(a,b) for a in nums for b in nums if a+b == target]
It assumes that two tuples which differ only by the order of elements are unique, and that an element can be used twice in the same tuple. If the definitions of the question are otherwise, then you can filter those tuples out of the returned value.

edited: see discussion below.
from itertools import combinations
list(set([(a,b) for a,b in combinations(sorted(nums),2) if a+b == target]))
This will remove duplicates as well.

Other version:
>>> nums = [4, 7, 6, 3, 5]
>>> target = 9
>>> set((a, target-a) for a in nums if target-a in set(nums))
{(4, 5), (5, 4), (3, 6), (6, 3)}
For every element a of nums, if target-a in also in nums, we have:
a + target-a = target (obvious);
a and target-a are both in nums.
Since we loop over every a, we get all the solutions.
To get rid of the duplicates (x, y) and (y, x):
>>> set((a, target-a) for a in nums if 2*a<=target and target-a in set(nums))
{(4, 5), (3, 6)}
Because 2*a <= target is equivalent to a <= target-a. When a > target-a and the requested conditions are met, we have a previous b = target-a so that (b, target-b) is a solution.

Related

Get Indeces of list of numbers Top N, next top N, and last top N

Given that I have a list of numbers:
raw_list = [10, 9, 2, 8, 1, 3, 5, 4, 6, 7,11]
I want to separate it to top N's three times. Which means I want to rank them.
# Top 6 rank as 3
# Next Top 4 rank as 2
# Last Top 1 rank as 1
ranked_list = [3, 3, 2, 3, 1, 2, 2, 2, 3, 3, 3]
What I tried:
sorted(range(len(raw_list)), key=lambda i: raw_list[i])[-2:]
But this only gives indeces of the topmost and not the next topmost value of the list.
Use:
lst = [10, 9, 2, 8, 1, 3, 5, 4, 6, 7, 11]
indices = sorted(range(len(lst)), key=lambda i: lst[i], reverse=True)
ranked_list = [0 for _ in range(len(lst))]
for i, j in enumerate(indices):
if i < 6:
ranked_list[j] = 3
elif i < 6 + 4:
ranked_list[j] = 2
else:
ranked_list[j] = 1
print(ranked_list)
Output
[3, 3, 2, 3, 1, 2, 2, 2, 3, 3, 3]
Here's a different approach which is significantly faster than the accepted answer (if that's important):
Edited to show performance timings between the original and accepted answer because #funnydman wants proof
from timeit import timeit
L = [10, 9, 2, 8, 1, 3, 5, 4, 6, 7, 11]
def func1(list_):
slist = sorted(list_)
result = []
top6 = set(slist[5:])
top4 = set(slist[1:5])
for e in list_:
if e in top6:
result.append(3)
elif e in top4:
result.append(2)
else:
result.append(1)
return result
def func2(list_):
indices = sorted(range(len(list_)), key=lambda i: list_[i], reverse=True)
ranked_list = [0 for _ in range(len(list_))]
for i, j in enumerate(indices):
if i < 6:
ranked_list[j] = 3
elif i < 6 + 4:
ranked_list[j] = 2
else:
ranked_list[j] = 1
return ranked_list
for func in func1, func2:
print(func.__name__, timeit(lambda: func(L)))
Output:
func1 1.3904414890002954
func2 2.388311982000232
IIUC, this will work for you:
import pandas as pd
list(pd.cut(l, bins=[0, 1, 5, 11], labels=[1, 2, 3]))
Output:
[3, 3, 2, 3, 1, 2, 2, 2, 3, 3, 3]

Algorithm to match people in a group based on who they are compatible with

I am trying to find the maximum matching in a group of people based on their compatibility. I was headed towards maximum cardinality matching on bipartite graphs when I realized I do not have 2 distinct groups.
Where I am at:
I have a list of their IDs: [1, 8, 3, 15, 13, 21]
I have a function called verify that will verify if 2 id's are compatible(there could be an odd number).
I then create a graph(dictionary) of the indices each persons indices is compatible with:
ids = [1, 8, 3, 15, 13, 21]
l = len(ids)
matches = {}
for x in range(l):
matches.setdefault(x,[])
for y in range(l):
if x != y:
if verify(ids[x],ids[y]):
matches[x].append(y)
this produces:
{0: [3, 4, 5], 1: [2, 4, 5], 2: [1, 5], 3: [0, 4, 5], 4: [0, 1, 3], 5: [0, 1, 2, 3]}
Now I am unsure where to go from here or if I should be taking another direction.
Can someone point me in the right direction please? Thanks
Looks as if you are trying to solve the Stable marriage problem. I wrote a Rosetta Code task that has solutions in Python and other languages.
it seems to me you're looking for the maximum clique in the graph, if you looking to find the biggest subset in a graph that all of the nodes are compatible (connected) with each other. for my knowledge this is np-complete problem, but maybe I got your question wrong.
I coded something up that works for your example:
from typing import List, Dict, Optional, Tuple
compat = {0: [3, 4, 5], 1: [2, 4, 5], 2: [1, 5], 3: [0, 4, 5], 4: [0, 1, 3], 5: [0, 1, 2, 3]}
def match(compat: Dict[int, List[int]]) -> Optional[List[Tuple[int, int]]]:
"""Match compatible people."""
c = sorted(compat.items())
matched: Dict[int, int] = {}
m = _match(c, matched)
if m:
m = {tuple(sorted([k, v])) for k, v in m.items()}
m = sorted(m)
return m
def _match(c, matched):
"""Match compatible people recursively."""
if not c:
return matched
person, compat = c[0]
if person in matched:
return _match(c[1:], matched)
else:
for comp in compat:
if not comp in matched:
matched[person] = comp
matched[comp] = person
if _match(c[1:], matched):
return matched
else:
del matched[person], matched[comp]
return None
print(match(compat)) # [(0, 3), (1, 4), (2, 5)]
Here is the logic that solved my code(have not taken the time to convert my code to a SO version:
It comes from this answer:
https://stackoverflow.com/a/3303557/12941578
Given the adjacency matrix I posted(which is basically a set of edges but why convert):
{0: [3, 4, 5], 1: [2, 4, 5], 2: [1, 5], 3: [0, 4, 5], 4: [0, 1, 3], 5: [0, 1, 2, 3]}
Smallest vertex is 2: [1, 5] so you take the 2,1 point and remove the vertex.
You then remove all versions of 2,1. (if its a vertex, remove it all)
This leaves you with:
{0: [3, 4, 5], 3: [0, 4, 5], 4: [0, 3], 5: [0, 3]} This is 1 match
Repeat logic(4,0) and you get: This is match 2
{3: [5], 5: [3]}
Finally repeat and you get your match 3.
I am assuming this is based off an algorithm but I do not know which one.
This is incredibly fast compared to my first solution of creating all the options for matches and then seeing which version was the largest
Thanks
This algorithm tries to pick the maximum pairs from input using an iterative approach and usually comes within one pair of a more exhaustive search pattern on randomised data, but takes a fraction of the time to run
compats = [{0: [3, 4, 5], 1: [2, 4, 5], 2: [1, 5], 3: [0, 4, 5], 4: [0, 1, 3], 5: [0, 1, 2, 3]},
{0: [4, 1], 1: [4, 0], 2: [4], 3: [4], 4: [0, 1, 2, 3]}]
def _check(compat):
"""Check compatibilities are mutual."""
compat2 = {k:set(v) for k, v in compat.items()}
for p0, comp in compat2.items():
for p1 in comp:
assert p0 in compat2[p1], \
f"{p0} compat with {p1} but {p1} not compat with {p0}?"
every_item = set()
for v in compat2.values():
every_item |= v
diffs = set(compat).symmetric_difference(every_item)
assert not diffs, f"compat inner list items != compat keys: {diffs}"
def most_matched2(compat: Dict[int, List[int]]) -> Optional[List[Tuple[int, int]]]:
"""Match the most of the compatible people, iterably."""
def reorder(c_list: List[Tuple[Any, List[Any]]]) -> List[Tuple[Any, Any]]:
"""
Reorder compatibles; most compat first, their individual compatibilities
sorted least compat first.
( reorder([(0, [4, 1]), (1, [4, 0]), (2, [4]), (3, [4]), (4, [0, 1, 2, 3])])
== [(4, [3, 2, 1, 0]), (0, [1, 4]), (1, [0, 4]), (2, [4]), (3, [4])] )
"""
# sort most number of compatible first
c_list.sort(key=lambda x:(-len(x[1]), x))
# sort compatibles least compatible first
order = [x[0] for x in c_list]
for person, comp in c_list:
#print(person, comp)
comp.sort(key=lambda x:-order.index(x))
return c_list
c = [(k, list(v)) for k, v in compat.items()]
matches = set()
while c:
c = reorder(c)
p0, p1 = c[0][0], c[0][1][0]
matches.add(tuple(sorted((p0, p1))))
p0p1_set = {p0, p1}
c = [(p, remain) for p, comp in c
if p not in p0p1_set and (remain := list(set(comp) - p0p1_set))]
return sorted(matches)
for compat in compats:
_check(compat)
print(most_matched2(compat))
print()
# %% test gen
from itertools import combinations
from random import sample, randint
from collections import defaultdict
from time import monotonic
import pandas as pd
def _test_gen(n, k):
possibles = list(set(c) for c in combinations(range(n), 2))
if type(k) in {int, float}:
k = int(min(k, len(possibles)))
pairs = []
while len(pairs) < k and possibles:
p = possibles.pop(randint(0, len(possibles) - 1))
pairs.append(p)
if randint(0, 1):
possibles = [x for x in possibles if not p.intersection(x)]
compatibles = sorted(tuple(sorted(comp)) for comp in pairs)
data = defaultdict(set)
for p1, p2 in compatibles:
data[p1].add(p2)
data[p2].add(p1)
data = {key: sorted(val - {key}) for key, val in data.items()}
return data, [sorted(x) for x in pairs]
compat, pairs = _test_gen(4, 3)
print("\n## Random tests.")
for n, k in [(4, 5), (8, 5), (8, 5), (20, 40)]:
compat, pairs = _test_gen(n, k)
_check(compat)
print(ans1 := most_matched2(compat))

How to find sum of list subsets with a recursive function Python

I need to write a recursive function to return sums of all possible subsets.
I wrote the following code:
def subsets_sums(lst):
if len(lst) == 0:
return 0
else:
sum_list = [sum(lst)]
for i in range(len(lst)):
index_list = lst.copy()
del index_list[i]
test_list = subsets_sums(index_list)
sum_list = test_list, sum_list
return sum_list
But the output is very inelegant due to all the lists concatenations.
something like:
(((0, [10]), ((0, [3]), [13])), (((0, [10]), ((0, [6]), [16])), (((0, [3]), ((0, [6]), [9])), [19])))
for the list:
[10,3,6]
How can I make it appear in a list like:
[0,10,0,3,13,0,10,0,6,16,0,3,0,6,9,19]
Can I change something within the code?
The possible sums of nums are:
nums[0] + s where s is a possible sum of nums[1:], i.e. those including the first number.
All the possible sums of nums[1:], i.e. those not including the first number.
The base case of the recursion is that the empty list has only 0 as a possible sum. We'll use sets to avoid duplicates in the output; a sum is either possible or it isn't.
def subset_sums(nums):
if not nums:
return {0}
else:
rest_sums = subset_sums(nums[1:])
return { nums[0] + s for s in rest_sums } | rest_sums
Example:
>>> subset_sums([10, 3, 6])
{0, 3, 6, 9, 10, 13, 16, 19}
to get your desired output you can try:
lst = [10, 3, 6]
def subsets_sums(lst):
if len(lst) == 0:
return [0]
else:
sum_list = [sum(lst)]
for i in range(len(lst)):
index_list = lst.copy()
del index_list[i]
test_list = subsets_sums(index_list)
sum_list = test_list + sum_list
return sum_list
print(subsets_sums(lst))
output:
[0, 10, 0, 3, 13, 0, 10, 0, 6, 16, 0, 3, 0, 6, 9, 19]
Here's a method that shows us all the possible subsets together with their sum including the empty set. By using an index to iterate over A, we avoid copying the list each time if we were to use the sublist operator list[:].
def f(A, i=0):
if i == len(A):
return [([], 0)]
rest = f(A, i + 1)
return rest + [(s + [A[i]], _s + A[i]) for s, _s in rest]
print f([1, 2, 3])
Output:
[([], 0), ([3], 3), ([2], 2), ([3, 2], 5), ([1], 1), ([3, 1], 4), ([2, 1], 3), ([3, 2, 1], 6)]

Counting identical list-elements in a row in Python

I am using Python3. I have a list a of only integers. Now, I want to save the element and the number it repeats itself in a row in another list.
Example:
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
Output:
result = ["6,1", "0, 2", "2, 4", "1, 1", "89, 2"]
# the number before the "," represents the element, the number after the "," represents how many times it repeats itself.
How to efficiently achieve my goal ?
I believe all the solutions given are counting the total occurrences of a number in the list rather than counting the repeating runs of a number.
Here is a solution using groupby from itertools. It gathers the runs and appends them to a dictionary keyed by the number.
from itertools import groupby
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
d = dict()
for k, v in groupby(a):
d.setdefault(k, []).append(len(list(v)))
Dictionary created:
>>> d
{6: [1], 0: [2], 2: [4], 1: [1], 89: [2]}
Note that all runs only had 1 count in their list. If there where other occurrences of a number already seen, there would be multiple counts in the lists (that are the values for dictionary).
for counting an individual element,
us list.count,
i.e, here, for, say 2, we user
a.count(2),
which outputs 4,
also,
set(a) gives the unique elements in a
overall answer,
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
nums = set(a)
result = [f"{val}, {a.count(val)}" for val in set(a)]
print(result)
which gives
['0, 2', '1, 1', '2, 4', '6, 1', '89, 2']
Method 1: using for loop
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
result = []
a_set = set(a) # transform the list into a set to have unique integer
for nbr in a_set:
nbr_count = a.count(nbr)
result.append("{},{}".format(nbr, nbr_count))
print(result) # ['0,2', '1,1', '2,4', '6,1', '89,2']
Method 2: using list-comprehensions
result = ["{},{}".format(item, a.count(item)) for item in set(a)]
print(result) # ['0,2', '1,1', '2,4', '6,1', '89,2']
you can use Python List count() Method, method returns the number of elements with the specified value.
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
print ({x:a.count(x) for x in a})
output:
{6: 1, 0: 2, 2: 4, 1: 1, 89: 2}
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
dic = dict()
for i in a:
if(i in dic):
dic[i] = dic[i] + 1
else:
dic[i] = 1
result = []
for i in dic:
result.append(str(i) +"," + str(dic[i]))
Or:
from collections import Counter
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
mylist = [Counter(a)]
print(mylist)
You can use Counter from collections:
from collections import Counter
a = [6, 0, 0, 2, 2, 2, 2, 1, 89, 89]
counter = Counter(a)
result = ['{},{}'.format(k, v) for k,v in counter.items()]

how to find the count the difference back to the previous zero in a list?

I have below list
[7, 2, 0, 3, 4, 2, 5, 0, 3, 4]
and the result list is
[1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
the result obtained by with the below logic
For each value, count the difference back to the previous zero (or the start of the Series,
whichever is closer).
am trying to implement , but not able to get .
So how to find the previous zero position , such that we can get that series ?
I tried below , but somehow it is failing , and it is seems not good solution
import pandas as pd
df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
#print(df)
df_series = df['X']
print(df_series.iloc[-1])
target_series = pd.Series([])
#print(target_series)
def calculate_value(i,actual_index):
if(df_series.iloc[i-1] == 0):
if(i < 0):
zero_position = i + df_series.size-1
if(actual_index - 0 < zero_position):
target_series[actual_index]=actual_index+1
return
else:
target_series[actual_index]=zero_position
return
else:
target_series[actual_index]=target_series[actual_index]+1
return
else:
if(i+df_series.size != actual_index):
calculate_value(i-1,actual_index)
for i in df.index:
if(df_series[i]==0 and i!=0):
target_series[i]=0
elif(df_series[i]!=0 and i==0):
target_series[i]=1
else:
calculate_value(i,i)
print(target_series)
If you want a Pandas one-liner solution:
import pandas as pd
s = pd.Series([7, 2, 0, 3, 4, 2, 5, 0, 3, 4])
(s.groupby(s.eq(0).cumsum().mask(s.eq(0))).cumcount() + 1).mask(s.eq(0), 0).tolist()
Output:
[1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
If you stick to the list you can get your result quite easily:
l = [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]
i = 0
r = []
for element in l:
if element != 0:
i += 1
else:
i = 0
r.append(i)
r
#[1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
Here is a working solution for you:
a = [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]
b = []
z_pos = -1
for i, n in enumerate(a):
if n == 0:
z_pos = i
b.append(i - z_pos)
print(b) # [1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
It does not use anything too fancy so I explaining its internal workings is unnecessary I think. If however there is something that is not clear to you let me know.
If you want to use python only, here is the solution:
a = [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]
z = None
b = []
for i in range(len(a)):
if a[i] != 0 and z== None:
b.append(i+1)
elif a[i] == 0:
b.append(0)
z = 0
else:
z += 1
b.append(z)
b is the required list.
Here is how to do it with a standard list, not Pandas dataframe, but the logic is the same
arr = [7,2,0,3,4,5,1,0,2]
arr2 = []
counter = 1
for item in arr:
if(item==0):
counter = 0
arr2.append(counter)
counter+=1
print(arr2)
You can see the code working here.
Some explanations:
What you want is a sort of counter between two 0 in your array.
So you loop over your array, and each time you encounter a 0 you reset your counter, and you insert the value of your counter each iteration of the loop.
If you are a fan of shortness instead of readability or effectiveness, you can also roll with this:
a = [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]
distances = [([0]+a)[:idx][::-1].index(0) for idx in range(1, len(a)+2)][1:]
Which gives the desired result:
print(distances)
>> [1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
Solution with no for loops or apply, just some pandas groupby fun.
We use
df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
# make a new column with zeros at zeros and nans elsewhere
df = df.assign(idx_from_0=df.loc[df.X==0])
nul = df['idx_from_0'].isnull()
df.assign(idx_from_0=nul.groupby((nul.diff() == 1).cumsum()).cumsum())
Out[1]:
X idx_from_0
0 7 1.0
1 2 2.0
2 0 0.0
3 3 1.0
4 4 2.0
5 2 3.0
6 5 4.0
7 0 0.0
8 3 1.0
9 4 2.0
The cumsum forward fill add one was taken from this answer.

Categories

Resources