Find linked elements betwenn two list - python

Probably this is a very stupid mistake but I can't find a solution. I have the lists below:
node_pair = [('a', 'b'), ('b', 'a')]
edge_list = [
(1, 'unuseful thing', {'orig-id': 'a'}),
(11, 'unuseful thing', {'orig-id': 'b'}),
]
I need to find the numeric couple in edge_list that is related to node_pair. For ('a', 'b') I will see (1, 11) and for ('b', 'a') I will see (11, 1). Below my code:
for pair in node_pair:
start_node = pair[0][0]
end_node = pair[1][0]
print(f'from {start_node} --> to {end_node}')
graph_start_end_node = []
for edge in edge_list:
edge_orig_id = edge[2]['orig-id']
if start_node == edge_orig_id:
edge_start_node = edge[0]
graph_start_end_node.append(edge_start_node)
if end_node == edge_orig_id:
edge_start_node = edge[0]
graph_start_end_node.append(edge_start_node)
print(graph_start_end_node)
The result is:
from a --> to b
[1, 11]
from b --> to a
[1, 11]

Using append works only when the order in the node_pair list is the same as in the edge_list, because this way the value is added at the end of the list. When the order could vary you have to place the matched value in a specific position - like:
graph_start_end_node = [None, None]
for edge in edge_list:
edge_orig_id = edge[2]['orig-id']
if start_node == edge_orig_id:
edge_start_node = edge[0]
graph_start_end_node[0] = edge_start_node
if end_node == edge_orig_id:
edge_start_node = edge[0]
graph_start_end_node[1] = edge_start_node

I found it hard to fix your code so I tried it this way.
node_pair = [('a', 'b'), ('b', 'a')]
edge_list = [
(1, 'unuseful thing', {'orig-id': 'a'}),
(11, 'unuseful thing', {'orig-id': 'b'}),
]
for pair in node_pair:
start_node, end_node = pair
print(f'from {start_node} --> to {end_node}')
graph_start_end_node = [e[0] for p in pair for e in edge_list if e[2]['orig-id'] == p ]
print(graph_start_end_node)
Make a new list with a list iteration is generally easier than trying to append to an existing one.

Related

Why listay is not appending the elements?

I'm very new in Python and coding in general, so this question probably will sound dumb.
I want to append tuples with two elements in listay: if the first element of l2 matches with any first element of listax, then it would be appended as a tuple in listay with its second element.
If it worked my output (print(listay)) would be: ['a',4),('b',2), ('c',1)]. Instead, the output is an empty list. What am I doing wrong?
Also, I am sorry if I am not offering all the information necessary. This is my first question ever about coding in a forum.
import operator
listax= []
listay= []
l1= [('a',3), ('b',3), ('c',3), ('d',2)]
l2= [('a',4),('b',2), ('c',1), ('d',2)]
sl1= sorted(l1, key= lambda t: t[1])
sl2= sorted(l2, key= lambda t: t[1])
tup1l1= sl1[len(sl1)-1]
k1l1= tup1l1[0]
v1l1= tup1l1[1]
tup2l1= sl1[len(sl1)-2]
k2l1= tup2l1[0]
v2l1= tup2l1[1]
tup3l1= sl1[len(sl1)-3]
k3l1= tup3l1[0]
v3l1= tup3l1[1]
tup1l2= sl2[len(sl2)-1]
k1l2= tup1l2[0]
v1l2= tup1l2[1]
tup2l2= sl2[len(sl2)-2]
k2l2= tup2l2[0]
v2l2= tup2l2[1]
tup3l2= sl2[len(sl2)-3]
k3l2= tup3l2[0]
v3l2= tup3l2[1]
listax.append((k2l1, v2l1))
if v2l1== v1l1:
listax.append((k1l1, v1l1))
if v2l1== v3l1:
listax.append((k3l1, v3l1))
for i,n in l2:
if i in listax:
listay.append((i,n))
print(listay)
I'll play the debugger role here, because I'm not sure what are you trying to achieve, but you could do it yourself - try out breakpoint() build-in function and python debugger commands - it helps immensely ;)
Side note - I'm not sure why you import operator, but I assume it's not related to question.
You sort lists by the second element, ascending, python sort is stable, so you get:
sl1 = [('d', 2), ('a', 3), ('b', 3), ('c', 3)]
sl2 = [('c', 1), ('b', 2), ('d', 2), ('a', 4)]
k1l1 = 'c'
v1l1 = 3
k2l1 = 'b'
v2k1 = 3
k3l1 = 'a'
v3l1 = 3
k1l2 = 'a'
v1l2 = 4
k2l2 = 'd'
v2k2 = 2
k3l2 = 'b'
v3l2 = 2
after append
listax = [('b', 3)]
v2l1 == v1l1 is True 3 == 3, so
listax = [('b', 3), ('c', 3)]
v2l1 == v3l1 is True 3 == 3, so
listax = [('b', 3), ('c', 3), ('a', 3)]
I think it gets tricky here:
for i,n in l2:
with
l2 = [('a', 4), ('b', 2), ('c', 1), ('d', 2)]
we get
i = 'a'
n = 4
maybe you wanted enumerate(l2)?
'a' in listax ([('b', 3), ('c', 3), ('a', 3)]) is False
listax doesn't contain an element equal to 'a' - it contains an element, which contains the element 'a'. Maybe that's the mistake?
i = 'b'
n = 3
just like before
nothing interesting happens later ;)
Hope this helps :D

How do I print out the elements that is in both tuples?

a = (('we', 23), ('b', 2))
b = (('we', 3), ('e', 3), ('b', 4))
#wanted_result = (('we', 3), ('b', 4), ('we', 23), ('b', 2))
How can I receive the tuple that contains the same string in both a and b
like the result I have written below the code?
I would prefer using list comprehensions using filters btw... would that be available?
You can use set intersection:
keys = dict(a).keys() & dict(b)
tuple(t for t in a + b if t[0] in keys)
You can make a set of the intersection between the first part of the tuples in both lists. Then use a list comprehension to extract the tuples that match this common set:
a = (('we', 23), ('b', 2))
b = (('we', 3), ('e', 3), ('b', 4))
common = set(next(zip(*a))) & set(next(zip(*b)))
result = [t for t in a+b if t[0] in common]
[('we', 23), ('b', 2), ('we', 3), ('b', 4)]
You can also do something similar using the Counter class from collections (by filtering tuples on string counts greater than 1:
from collections import Counter
common = Counter(next(zip(*a,*b)))
result = [(s,n) for (s,n) in a+b if common[s]>1]
If you want a single list comprehension, given that your tuples have exactly two values, you can pair each one with a dictionary formed form the other and use the dictionary as a filter mechanism:
result = [t for d,tl in [(dict(b),a),(dict(a),b)] for t in tl if t[0] in d]
Adding two list comprehensions (i.e. concatenating lists):
print([bi for bi in b if any(bi[0]==i[0] for i in a)] +
[ai for ai in a if any(ai[0]==i[0] for i in b)])
# Output: [('we', 3), ('b', 4), ('we', 23), ('b', 2)]
Explanation
[bi for bi in b if any(bi[0]==i[0] for i in a)] # ->>
# Take tuples from b whose first element equals one of the
# first elements of a
[ai for ai in a if ai[0] in [i[0] for i in b]]
# Similarly take tuples from a whose first elements equals one of the
# first elements of b
another variation with sets
filtered_keys=set(k for k,v in a)&set(k for k,v in b)
res=tuple((k, v) for k, v in [*a, *b] if k in filtered_keys)
>>> (('we', 23), ('b', 2), ('we', 3), ('b', 4))

How to find intersections of ranges of float tuples across two lists in python?

I have two lists of the form:
lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 23)]
The output I am looking to get is:
output = [(1.2, 3), (6.55, 8)]
Basically, I want the intersections between the ranges defined by the tuples across the two lists.
You can assume-
the indices to be ordered within a given list. For example, in lst2:
1 < 6.55 < 22
the ranges to be valid (within a tuple, the startVal<=endEndVal). In lst2:
1 < 3 and 6.55 < 14.871 and 22 < 23
What is an efficient way to achieve this?
I think the best way to do this is with a list comprehension, given that both lists are the same length.
in two lists for readability:
# get the min max ranges
a = [(max(i[0], j[0]),min(i[1],j[1])) for i,j in zip(lst1, lst2)]
# check that min is smaller than max
a = [(i,j) for (i,j) in a if i < j]
or in one list:
a = [(i,j) for (i,j) in [(max(i[0], j[0]),min(i[1],j[1])) for i,j in zip(lst1, lst2)] if i < j]
Using itertools and heapq.merge:
lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 25)]
from heapq import merge
from itertools import tee, groupby
m1, m2 = tee(merge(lst1, lst2, key=lambda k: k[0]))
next(m2, None)
out = []
for v, g in groupby(zip(m1, m2), lambda k: k[0][1] < k[1][0]):
if not v:
l = [*g][0]
out.append((max(i[0] for i in l), min(i[1] for i in l)))
print(out)
Prints:
[(1.2, 3), (6.55, 8), (24.5, 25)]
A solution using iterators. I use a while loop which stays active until both iterators running on the lists are exausthed.
lst1 = [(1.2, 4), (5, 8), (19, 21), (24.5, 26)]
lst2 = [(1, 3), (6.55, 14.871), (22, 23)]
itr1 = iter(lst1)
itr2 = iter(lst2)
on1 = True
on2 = True
rng1 = next(itr1)
rng2 = next(itr2)
res = []
while on1 or on2:
ll = max(rng1[0], rng2[0])
rr = min(rng1[1], rng2[1])
if ll < rr:
res.append((ll, rr))
if on1 and on2:
if rng1[0] < rng2[0]:
try:
rng1 = next(itr1)
except StopIteration:
on1 = False
else:
try:
rng2 = next(itr2)
except StopIteration:
on2 = False
elif on1:
try:
rng1 = next(itr1)
except StopIteration:
on1 = False
elif on2:
try:
rng2 = next(itr2)
except StopIteration:
on2 = False
if len(res) > 1 and res[-1] == res[-2]:
res.pop(-1)
print(res)
Using your sample input, this prints: [(1.2, 3), (6.55, 8)]
make a simple function to get an intersection range for two range.
def get_intersect(r1, r2):
left = max(r1[0], r2[0])
right = min(r1[1], r2[1])
if left>right:
return None
return (left,right)
and get all intersections though the double loop
for i1 in lst1:
for i2 in lst2:
ia = get_intersect(i1, i2)
if ia!=None:
print(ia)
It would be faster, if you add some conditions in the loop.

Remove both adjacent tuples from lists

Given this list:
[(1, 's'), (2, 'e'), (2, 's'), (3, 'e')]
This is a representation of potentially overlapping intervals, e.g. 1 --> 2 and 2 --> 3, I've brought it into this representation for easier processing (see this answer for context)
I'd like to remove the pair (2, 'e') -- (2, 's') because the end (e) of the one interval is at the same number (2) as start (s) of the next interval. So the result should be
[(1, 's'), (3, 'e')]
And would represent 1 --> 3.
Edit: It's also possible that the intervals are overlapping, e.g. 1-->4 and 2-->3. That would be represented in this list of tuples (Note that the list is already sorted): [(1, 's'), (2, 's'), (3, 'e'), (4, 'e')]. In this case nothing needs to be done as no two tuples share the same number.
I've come up with this reduce:
import functools
functools.reduce(lambda l,i: l[:-1] if i[0] == l[-1][0] and i[1] != l[-1][1] else l + [i], a[1:], [a[0]])
Are there nicer ways to achieve that?
You can use itertools.groupby for a slightly longer (two lines), although more readable solution:
import itertools
def get_combinations(s):
new_data = [(a, list(b)) for a, b in itertools.groupby(s, key=lambda x:x[0])]
return [b[-1] for i, [a, b] in enumerate(new_data) if len(b) == 1 or len(b) > 1 and i == len(new_data) - 1]
print(get_combinations([(1, 's'), (2, 'e'), (2, 's'), (2, 'e')]))
print(get_combinations([(1, 's'), (2, 'e'), (2, 's'), (3, 'e')]))
Output:
[(1, 's'), (2, 'e')]
[(1, 's'), (3, 'e')]
I've been toying with functional languages a lot lately, so this may read less Pythonic than some, but I would use a (modified) itertools's pairwise recipe to iterate through by pairs
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None) # advance the second iterator
return itertools.zip_longest(a, b, fillvalue=(None, None))
then filter by which pairs don't match each other:
def my_filter(a, b):
a_idx, a_type = a
b_idx, b_type = b
if a_idx == b_idx and a_type == "e" and b_type == "s":
return False
return True
Filter them yourself (because a naive filter will allow the "start" value to live since it pairs with the element ahead of it)
def filter_them(some_list):
pairs = pairwise(some_list)
acc = []
while True:
try:
a, b = next(pairs)
if my_filter(a, b):
acc.append(a)
else:
next(pairs) # skip the next pair
except StopIteration:
break
return acc
I was tinkering about a "double continue" approach, and came up with this generator solution:
def remove_adjacent(l):
iterator = enumerate(l[:-1])
for i, el in iterator:
if el[0] == l[i+1][0] and el[1] != l[i+1][1]:
next(iterator)
continue
yield el
yield l[-1]

adding tuple elements in a list

l1=[(2,1),(3,2),(4,5)]
l2=[(2,3),(3,6),(11,3)]
I need:
result=[(2,4),(3,8),(4,5),(11,3)]
using for loop I was able to add tuples having the same first element.
>>> result = []
l1 = [(2, 1), (3, 2), (4, 5)]
l2 = [(2, 3), (3, 6), (11, 3)]
for x, y in l1:
for p, q in l2:
if x == p:
result.append((x, (y + q)))
>>> result
[(2, 4), (3, 8)]
How do I further add (4,5),(11,3)
So many ways to achieve the same goal... I like it!
Here is my solution.
l1=[(2,1),(3,2),(4,5)]
l2=[(2,3),(3,6),(11,3)]
d1 = dict(l1)
d2 = dict(l2)
result = []
for k in d1:
if k in d2:
result.append((k,d1[k]+d2[k]))
else:
result.append((k,d1[k]))
for k in d2:
if k not in d1:
result.append((k,d2[k]))
>>>print result
[(2, 4), (3, 8), (4, 5), (11, 3)]
There's no need to go through the elements of l2 for all elements of l1 if I understand what you're going for. Just use zip (or izip_longest if the lists have an uneven size) to use a tuple from each list and then append the first item and the sum if the first item matches and extend both tuples if they don't:
for tup1, tup2 in zip(l1, l2):
if tup1[0] == tup2[0]:
result.append((tup1[0], (tup1[1] + tup2[1])))
else:
result.extend([tup1, tup2])
With your input, this returns the required output:
>>> result
... [(2, 4), (3, 8), (4, 5), (11, 3)]
It can be easily done by using dict formed with l1 and iterating over l2, see working code snippet below:
l1=[(2,1),(3,2),(4,5)]
l2=[(2,3),(3,6),(11,3)]
result = dict(l1)
for y in l2:
result[y[0]] = result.get(y[0],0) + y[1]
result = result.items()
print result #[(11, 3), (2, 4), (3, 8), (4, 5)]
Or simply build results by iterating over concatenation of l1 and l2:
l1=[(2,1),(3,2),(4,5)]
l2=[(2,3),(3,6),(11,3)]
result = {}
for item in l1 + l2:
result[item[0]] = result.get(item[0],0) + item[1]
Good luck!
Seems natural to use a dictionary to keep track of which keys are already used. You can reduce the code size by using defaultdict:
import collections
l1=[(2,1),(3,2),(4,5)]
l2=[(2,3),(3,6),(11,3)]
d = collections.defaultdict(int)
for x,y in l1 + l2:
d[x] += y
print sorted(d.items())

Categories

Resources