Searching for intersections in two tuples of tuples in python - python

Having the following problem. I'm reading the data from stdin and save it in list that I convert to tuple the following way:
x = int(input())
f = []
for i in range(x):
a, b = map(int, input().split())
f.append([a,b])
def to_tuple(lst):
return tuple(to_tuple(i) if isinstance(i, list) else i for i in lst)
After this I receive two tuples of tuples looking something like that:
f = ((0, 1), (1, 2), (0, 2), (0, 3))
s = (((0,), (1, 2, 3)), ((0, 1), (2, 3)), ((0, 1, 2), (3,)))
What I'm trying to do is to find the number of intersections between all inner tuples of f and each tuple of s. In my case "intersection" should be considered as an "edges" between tuples (so in f we have all possible "edges" and checking if there will be an edge between inner tuples in particular tuple of s). So for the example it should print [3,3,1].
Basically, I know how to do in the simple case of intersection - so one can just use set() and then apply a.intersection(b) But how should I proceed in my case?
Many thanks and sorry if the question was already asked before :=)

I am sure this can be solve by different ways. but I believe this is the easiest.
out = set() # holds the output
for ff in f: # loop through f tuple
ff = set(ff) # convert to set
for ss1,ss2 in s: # loop through s tuple
# you can select which tuple to do the intersection on.
# here I am doing the intersection on both inner tuples in the s tuple.
ss1 = set(ss1) # convert to set
ss2 = set(ss2)
out.update(ff.intersection(ss1)) # intersection and add to out
out.update(ff.intersection(ss2)) # intersection and add to out
#if you want your output to be in list format
out = list(out)

This is an example of how you can proceed
a = ((1,1),(1,2))
b = (((1,2),(3,1)),((3,2),(1,2)),((1,4),))
l=[]
for t in b:
c=[i for i in a for j in t if i==j]
l.append(c)
print(l)

General answer for overall amount of edges:
def cnt_edges(a,b):
edge_cnt = 0
for i in range(len(a)):
node1 = a[i][0]
node2 = a[i][1]
for j in range(len(b)):
inner_node1 = b[j][0]
inner_node2 = b[j][1]
if (node1 in inner_node1 and node2 in inner_node2) or (node1 in inner_node2 and node2 in inner_node1):
edge_cnt += 1
return edge_cnt
a = ((0, 1),(0, 2), (0,3))
b = (((0),(1,2,3)), ((0,1),(2,3)), ((0,1,2),(3)))
cnt_edges(a,b)

Related

Complex heapq lazy merge (minimize space used)

Given a list of sorted lists, I wish to produce a sorted list of output.
This would be easy:
nums : List[List[int]]
h = heapq.merge(nums)
However, I also wish to tag each element of the output, with the index of the inner list from which it originated. Eg.
nums = [[1,3,5], [2,6], [4]]
h = _ # ???
for x in h:
print(x)
# Outputs
# (1,0)
# (2,1)
# (3,0)
# (4,2)
# (5,0)
# (6,1)
I have written a version that works,
h = heapq.merge(*map(lambda l: map(lambda x: (x,l[0]), l[1]), enumerate(nums)))
But I'm afraid I might have lost a desirable space-complexity guarantee; how can I know whether the (transformed) inner lists are being manifested or not? (and what exactly does the * do in my attempt?)
Since default tuple comparison is lexicographic, you can convert the innermost-element x to (x, i) where i is the index of the list containing x in nums. For example,
import heapq
from itertools import repeat
nums = [[1,3,5], [2,6], [4]]
nums_with_inds = (zip(lst, repeat(i)) for i, lst in enumerate(nums))
res = heapq.merge(*nums_with_inds)
for tup in res:
print(tup)
# (1, 0)
# (2, 1)
# (3, 0)
# (4, 2)
# (5, 0)
# (6, 1)

Order dictionary with x and y coordinates in python

I have this problem.
I need order this points 1-7
1(4,2), 2(3, 5), 3(1,4), 4(1,1), 5(2,2), 6(1,3), 7(1,5)
and get this result
4 , 6 , 3 , 5 , 2 , 1 , 7.
I am using a python script for sort with x reference and is ok, but the sort in y is wrong.
I have tried with sorted(dicts,key=itemgetter(1,2))
Someone can help me please ?
Try this:
sorted(dicts,key=itemgetter(1,0))
Indexing in python starts at 0. itemgetter(1,0) is sorting by the second element and then by the first element
This sorts the code based on ordering the first coordinate of the tuple, and then sub-ordering by the second coordinate of the tuple. I.e. Like alphabetically where "Aa", then "Ab", then "Ba", then "Bb". More literall (1,1), (1,2), (2,1), (2,2), etc.
This will work IF (and only if) the tuple value pair associated with #7 is actually out of order in your question (and should actually be between #3 and #5.)
If this is NOT the case, See my other answer.
# Make it a dictionary, with the VALUETUPLES as the KEYS, and the designator as the value
d = {(1,1):4, (1,3):6, (1,4):3, (2,2):5, (3,5):2, (4,2):1,(1,5):7}
# ALSO make a list of just the value tuples
l = [ (1,1), (1,3), (1,4), (2,2), (3,5), (4,2), (1,5)]
# Sort the list by the first element in each tuple. ignoring the second
new = sorted(l, key=lambda x: x[0])
# Create a new dictionary, basically for temp sorting
new_d = {}
# This iterates through the first sorted list "new"
# and creates a dictionary where the key is the first number of value tuples
count = 0
# The extended range is because we don't know if any of the Tuple Values share any same numbers
for r in range(0, len(new)+1,1):
count += 1
new_d[r] = []
for item in new:
if item[0] == r:
new_d[r].append(item)
print(new_d) # So it makes sense
# Make a final list to capture the rdered TUPLES VALUES
final_list = []
# Go through the same rage as above
for r in range(0, len(new)+1,1):
_list = new_d[r] # Grab the first list item from the dic. Order does not matter here
if len(_list) > 0: # If the list has any values...
# Sort that list now by the SECOND tuple value
_list = sorted(_list, key=lambda x: x[1])
# Lists are ordered. So we can now just tack that ordered list onto the final list.
# The order remains
for item in _list:
final_list.append(item)
# This is all the tuple values in order
print(final_list)
# If you need them correlated to their original numbers
by_designator_num = []
for i in final_list: # The the first tupele value
by_designator_num.append(d[i]) # Use the tuple value as the key, to get the original designator number from the original "d" dictionary
print(by_designator_num)
OUTPUT:
[(1, 1), (1, 3), (1, 4), (1, 5), (2, 2), (3, 5), (4, 2)]
[4, 6, 3, 7, 5, 2, 1]
Since you're searching visually from top-to-bottom, then left-to-right, this code is much simpler and provides the correct result. It basically does the equivalent of a visual scan, by checking for all tuples that are at each "y=n" position, and then sorting any "y=n" tuples based on the second number (left-to-right).
Just to be more consistent with the Cartesian number system, I've converted the points on the graph to (x,y) coordinates, with X-positive (increasing to the right) and y-negative (decreasing as they go down).
d = {(2,-4):1, (5,-3):2, (4,-1):3, (1,-1):4, (2,-2):5, (3,-1):6, (1,-5):7}
l = [(2,-4), (5,-3), (4,-1), (1,-1), (2,-2), (3,-1), (1,-5)]
results = []
# Use the length of the list. Its more than needed, but guarantees enough loops
for y in range(0, -len(l), -1):
# For ONLY the items found at the specified y coordinate
temp_list = []
for i in l: # Loop through ALL the items in the list
if i[1] == y: # If tuple is at this "y" coordinate then...
temp_list.append(i) # ... append it to the temp list
# Now sort the list based on the "x" position of the coordinate
temp_list = sorted(temp_list, key=lambda x: x[0])
results += temp_list # And just append it to the final result list
# Final TUPLES in order
print(results)
# If you need them correlated to their original numbers
by_designator_num = []
for i in results: # The the first tupele value
by_designator_num.append(d[i]) # Use the tuple value as the key, to get the original designator number from the original "d" dictionary
print(by_designator_num)
OR if you want it faster and more compact
d = {(2,-4):1, (5,-3):2, (4,-1):3, (1,-1):4, (2,-2):5, (3,-1):6, (1,-5):7}
l = [(2,-4), (5,-3), (4,-1), (1,-1), (2,-2), (3,-1), (1,-5)]
results = []
for y in range(0, -len(l), -1):
results += sorted([i for i in l if i[1] == y ], key=lambda x: x[0])
print(results)
by_designator_num = [d[i] for i in results]
print(by_designator_num)
OUTPUT:
[(1, -1), (3, -1), (4, -1), (2, -2), (5, -3), (2, -4), (1, -5)]
[4, 6, 3, 5, 2, 1, 7]

How to find if list instance is available in another list or not?

I have two sets of lists.
list A= [(1,6),(3,10),(4,1),(0,5)]
list B = [(0,3),(0,4),(30,1),(4,10)]
Now for each item in B, I have to check if its available in list A or not for a threshold of -2 to +2.
So the very first value in B which is (0,3), for that using threshold in each point I have found that (from -2 to 2, from 1 to 5) within this rage a list item is available in the list A or not. We can see that the last item value (0,5) fulfil this condition. so I can say that item (0,3) is in list A.Now I have to put this value in a new list.
According to the process, my new list would be :
[(0,3),(0,4),(4,10)]
I will be so glad if somebody tells me how to achieve this.
Here is what i think you are looking for
A= [(1,6),(3,10),(4,1),(0,5)]
B = [(0,3),(0,4),(30,1),(4,10)]
result=[x for x in B if any(x[0]-2<=a[0]<=x[0]+2 and x[1]-2<=a[1]<=x[1]+2 for a in A)]
print(result)
output :
[(0, 3), (0, 4), (4, 10)]
You can use taxi cab geometry:
def manhattan(as_, b):
threshold = 4
for a in as_:
p1, p2 = a
q1, q2 = b
dist = abs(p1 - q1) + abs(p2 - q2)
if dist <= threshold:
return b
else:
continue
t = list(filter(lambda i: manhattan(listA, i), listB))
[(0, 3), (0, 4), (4, 10)]
Or
from operator import truth
t = list(filter(truth, (manhattan(listA, i) for i in listB)))

Better ways to find pairs that sum to N

Is there a faster way to write this, the function takes a list and a value to find the pairs of numeric values in that list that sum to N without duplicates I tried to make it faster by using sets instead of using the list itself (however I used count() which I know is is linear time) any suggestions I know there is probably a way
def pairsum_n(list1, value):
set1 = set(list1)
solution = {(min(i, value - i) , max(i, value - i)) for i in set1 if value - i in set1}
solution.remove((value/2,value/2)) if list1.count(value/2) < 2 else None
return solution
"""
Example: value = 10, list1 = [1,2,3,4,5,6,7,8,9]
pairsum_n = { (1,9), (2,8), (3,7), (4,6) }
Example: value = 10, list2 = [5,6,7,5,7,5,3]
pairsum_n = { (5,5), (3,7) }
"""
Your approach is quite good, it just needs a few tweaks to make it more efficient. itertools is convenient, but it's not really suitable for this task because it produces so many unwanted pairs. It's ok if the input list is small, but it's too slow if the input list is large.
We can avoid producing duplicates by looping over the numbers in order, stopping when i >= value/2, after using a set to get rid of dupes.
def pairsum_n(list1, value):
set1 = set(list1)
list1 = sorted(set1)
solution = []
maxi = value / 2
for i in list1:
if i >= maxi:
break
j = value - i
if j in set1:
solution.append((i, j))
return solution
Note that the original list1 is not modified. The assignment in this function creates a new local list1. If you do actually want (value/2, value/2) in the output, just change the break condition.
Here's a slightly more compact version.
def pairsum_n(list1, value):
set1 = set(list1)
solution = []
for i in sorted(set1):
j = value - i
if i >= j:
break
if j in set1:
solution.append((i, j))
return solution
It's possible to condense this further, eg using itertools.takewhile, but it will be harder to read and there won't be any improvement in efficiency.
Try this, running time O(nlogn):
v = [1, 2, 3, 4, 5, 6, 7, 8, 9]
l = 0
r = len(v)-1
def myFunc(v, value):
ans = []
% this block search for the pair (value//2, value//2)
if value % 2 == 0:
c = [i for i in v if i == value // 2]
if len(c) >= 2:
ans.append((c[0], c[1]))
v = list(set(v))
l = 0
r = len(v)-1
v.sort()
while l<len(v) and r >= 0 and l < r:
if v[l] + v[r] == value:
ans.append((v[l], v[r]))
l += 1
r -= 1
elif v[l] + v[r] < value:
l += 1
else:
r -= 1
return list(set(ans))
It is called the Two pointers technique and it works as follows. First of all, sort the array. This imposes a minimum running time of O(nlogn). Then set two pointers, one pointing at the start of the array l and other pointing at its last element r (pointers name are for left and right).
Now, look at the list. If the sum of the values returned at position l and r is lower than the value we are looking for, then we need to increment l. If it's greater, we need to decrement r.
If v[l] + v[r] == value than we can increment/decrement both l or r since in any case we want to skip the combination of values (v[l], v[r]) as we don't want duplicates.
Timings: this is actually slower then the other 2 solutions. Due to the amount of combinations produced but not actually needed it gets worse the bigger the lists are.
You can use itertools.combinations to produce the 2-tuple-combinations for you.
Put them into a set if they match your value, then return as set/list:
from itertools import combinations
def pairsum_n(list1, value):
"""Returns the unique list of pairs of combinations of numbers from
list1 that sum up `value`. Reorders the values to (min_value,max_value)."""
result = set()
for n in combinations(list1, 2):
if sum(n) == value:
result.add( (min(n),max(n)) )
return list(result)
# more ugly one-liner:
# return list(set(((min(n),max(n)) for n in combinations(list1,2) if sum(n)==value)))
data = [1,2,3,4,5,6,6,5,4,3,2,1]
print(pairsum_n(data,7))
Output:
[(1, 6), (2, 5), (3, 4)]
Fun little thing, with some sorting overhead you can get all at once:
def pairsum_n2(data, count_nums=2):
"""Generate a dict with all count_nums-tuples from data. Key into the
dict is the sum of all tuple-values."""
d = {}
for n in (tuple(sorted(p)) for p in combinations(data,count_nums)):
d.setdefault(sum(n),set()).add(n)
return d
get_all = pairsum_n2(data,2) # 2 == number of numbers to combine
for k in get_all:
print(k," -> ", get_all[k])
Output:
3 -> {(1, 2)}
4 -> {(1, 3), (2, 2)}
5 -> {(2, 3), (1, 4)}
6 -> {(1, 5), (2, 4), (3, 3)}
7 -> {(3, 4), (2, 5), (1, 6)}
2 -> {(1, 1)}
8 -> {(2, 6), (4, 4), (3, 5)}
9 -> {(4, 5), (3, 6)}
10 -> {(5, 5), (4, 6)}
11 -> {(5, 6)}
12 -> {(6, 6)}
And then just access the one you need via:
print(get_all.get(7,"Not possible")) # {(3, 4), (2, 5), (1, 6)}
print(get_all.get(17,"Not possible")) # Not possible
Have another solution, it's alot faster then the one I just wrote, not as fast as #PM 2Ring's answer:
def pairsum_n(list1, value):
set1 = set(list1)
if list1.count(value/2) < 2:
set1.remove(value/2)
return set((min(x, value - x) , max(x, value - x)) for x in filterfalse(lambda x: (value - x) not in set1, set1))

construct graph from python set type

The short question, is there an off the self function to make a graph from a collection of python sets?
The longer question: I have several python sets. They each overlap or some are sub sets of others. I would like to make a graph (as in nodes and edges) nodes are the elements in the sets. The edges are intersection of the sets with weighted by number of elements in the intersection of the sets. There are several graphing packages for python. (NetworkX, igraph,...) I am not familiar with the use of any of them. Will any of them make a graph directly from a list of sets ie, MakeGraphfromSets(alistofsets)
If not do you know of an example of how to take the list of sets to define the edges. It actually looks like it might be straight forward but an example is always good to have.
It's not too hard to code yourself:
def intersection_graph(sets):
adjacency_list = {}
for i, s1 in enumerate(sets):
for j, s2 in enumerate(sets):
if j == i:
continue
try:
lst = adjacency_list[i]
except KeyError:
adjacency_list[i] = lst = []
weight = len(s1.intersection(s2))
lst.append( (j, weight) )
return adjacency_list
This function numbers each set with its index within sets. We do this because dict keys must be immutable, which is true of integers but not sets.
Here's an example of how to use this function, and it's output:
>>> sets = [set([1,2,3]), set([2,3,4]), set([4,2])]
>>> intersection_graph(sets)
{0: [(1, 2), (2, 1)], 1: [(0, 2), (2, 2)], 2: [(0, 1), (1, 2)]}
def MakeGraphfromSets(sets):
egs = []
l = len(sets)
for i in range(l):
for j in range(i,l):
w = sets[i].intersection(sets[j])
egs.append((i,j,len(w)))
return egs
# (source set index,destination set index,length of intersection)
sets = [set([1,2,3]), set([2,3,4]), set([4,2])]
edges = MakeGraphfromSets(sets)
for e in edges:
print e
OUTPUT:
(0, 0, 3)
(0, 1, 2)
(0, 2, 1)
(1, 1, 3)
(1, 2, 2)
(2, 2, 2)

Categories