Related
My list of tuples look like this:
[(0, 0), (3, 0), (3, 3), (0, 3), (0, 0), (0, 6), (3, 6), (3, 9), (0, 9), (0, 6), (6, 0), (9, 0), (9, 3), (6, 3), (6, 0), (0, 3), (3, 3), (3, 6), (0, 6), (0, 3)]
It has the format of (X, Y) where I want to get the max and min of all Xs and Ys in this list.
It should be min(X)=0, max(X)=9, min(Y)=0, max(Y)=9
However, when I do this:
min(listoftuples)[0], max(listoftuples)[0]
min(listoftuples)[1], max(listoftuples)[1]
...for the Y values, the maximum value shown is 3 which is incorrect.
Why is that?
for the Y values, the maximum value shown is 3
because max(listoftuples) returns the tuple (9, 3), so max(listoftuples)[0] is 9 and max(listoftuples)[1] is 3.
By default, iterables are sorted/compared based on the values of the first index, then the value of the second index, and so on.
If you want to find the tuple with the maximum value in the second index, you need to use key function:
from operator import itemgetter
li = [(0, 0), (3, 0), ... ]
print(max(li, key=itemgetter(1)))
# or max(li, key=lambda t: t[1])
outputs
(3, 9)
Here is a simple way to do it using list comprehensions:
min([arr[i][0] for i in range(len(arr))])
max([arr[i][0] for i in range(len(arr))])
min([arr[i][1] for i in range(len(arr))])
max([arr[i][1] for i in range(len(arr))])
In this code, I have used a list comprehension to create a list of all X and all Y values and then found the min/max for each list. This produces your desired answer.
The first two lines are for the X values and the last two lines are for the Y values.
Tuples are ordered by their first value, then in case of a tie, by their second value (and so on). That means max(listoftuples) is (9, 3). See How does tuple comparison work in Python?
So to find the highest y-value, you have to look specifically at the second elements of the tuples. One way you could do that is by splitting the list into x-values and y-values, like this:
xs, ys = zip(*listoftuples)
Or if you find that confusing, you could use this instead, which is roughly equivalent:
xs, ys = ([t[i] for t in listoftuples] for i in range(2))
Then get each of their mins and maxes, like this:
x_min_max, y_min_max = [(min(L), max(L)) for L in (xs, ys)]
print(x_min_max, y_min_max) # -> (0, 9) (0, 9)
Another way is to use NumPy to treat listoftuples as a matrix.
import numpy as np
a = np.array(listoftuples)
x_min_max, y_min_max = [(min(column), max(column)) for column in a.T]
print(x_min_max, y_min_max) # -> (0, 9) (0, 9)
(There's probably a more idiomatic way to do this, but I'm not super familiar with NumPy.)
I am working on my phd and I am stuck on this step. The problem consists of implementing a finite element mesh merging algorithm and maybe my solution is not the best, so if you think of a better one I am open to suggestions.
Regarding the problem: I have a finite element mesh, which is composed of QUAD elements (squares with 4 nodes) and TRIA elements (triangles with 3 nodes). These elements are connected on edges, an edge is defined by 2 nodes (edge=[node1,node2]). I have a list of edges that I do not want to merge, but for the rest of the edges I want the program to merge the elements with the common edge.
As a simple example: assume I have 4 elements A,B,C and D (QUAD elms, defined by 4 nodes). The mesh looks something like this
1--------------2----------------3
| | |
| A | B |
| | |
4--------------5----------------6
| | |
| C | D |
| | |
7--------------8----------------9
These elements are defined in a dictionary:
mesh_dict={'A': [1,2,5,4], 'B':[2,3,6,5], 'C':[4,5,8,7],'D':[5,6,9,8]}
I also have a dictionary for the node position with values for X,Y,Z coordinates. Let's say I want to merge on edge [4,5] and [5,6].
My solution is the following: I start iterating through the elements in mesh_dict, I find the neighbors of the element with a function get_elm_neighbors(element), I check the angle between elements with function check_angle(elm1,elm2,angle) (I need the angle between elements to be below a certain threshold), than I check for which edge should be merged by get_edge_not_bar(), than I have a function which updates the nodes for the first element to complete the merging.
for e in mesh_dict:
if e not in delete_keys:
neighbors=get_elm_neighbors(e)
for key,value in neighbors.items():
check = check_angle(e,key,0.5)
if check:
nodes = get_edge_not_bar(value)
if nodes:
new_values=merge_elms(e,key,nodes)
d = {e: new_values}
mesh_dict_merged.update(d)
mesh_dict.update(d)
delete_keys.append(key)
My problem is that I need to delete the elements that remain after the merging. For example in the above case I start on element A and I merge on the edge [4,5], after that the elm A definition will be 'A':[1,2,8,7], then I need to delete elm C and proceed with the iteration.
My solution was to create a duplicate dictionary mesh_dict_merge in which I update the values for the elements and then delete the ones that I don't want to while iterating through the original dict but taking into consideration the deleted elements (deleted_keys list) to not go through them
I guess my question is if there is a way to iterate through the dictionary, update values and delete keys while doing so ? Or if there is a better solution to approach this problem, maybe iterate through nodes instead of elements ?
EDIT: changed 'A': [1,2,4,5] to 'A': [1,2,5,4]
It can be done updating the elements on-the-fly. But I should not recommend it because your algorithm will depend on the order you iterate the elements, and may be not deterministic. This mean that two meshes with identical geometry and topology could give different results depending on the labels you use.
The recommendation is :
Compute all dihedral angles in your mesh. Store those that are under your merge threshold.
Find the minimum angle and merge the two elements that share that edge.
Update the dihedral angles around the new element. This include removing angles from elements that have merged, and optionally include new angles for the new element.
Repeat from step 2 until every angle is over the threshold, or until the number of elements is the desired.
The optional part in step 3 allows to determine the aggressiveness of your method. Sometimes it is better not to include new angles and repeat several times the complete process to avoid focus the reduction too much in a zone.
I thought about how to find adjacent elements by finding elements that shared the same edge - but I had to have edges as a pair of end indices in sorted order.
I could then work out touches (should work for triangle elements too).
I introduce dont_merge as a set of ordered edge indices that cannot be merged away then merge into merged_ordered_edges and finally convert back to the mesh format of your original with edges going around each element.
I have commented out a call to check_angle(name1, name2) which you would have to add in. I assume that the check would succeed every time by the comment.
# -*- coding: utf-8 -*-
"""
Finite element mesh merge algorithm
https://stackoverflow.com/questions/59079755/how-to-merge-values-from-dictionary-on-different-keys-while-iterating-through-it
Created on Thu Nov 28 21:59:07 2019
#author: Paddy3118
"""
#%%
mesh_dict={'A': [1,2,5,4], 'B':[2,3,6,5], 'C':[4,5,8,7],'D':[5,6,9,8]}
#
ordered_edges = {k: {tuple(sorted(endpoints))
for endpoints in zip(v, v[1:] + v[:1])}
for k, v in mesh_dict.items()}
# = {'A': {(1, 2), (1, 4), (2, 5), (4, 5)},
# 'B': {(2, 3), (2, 5), (3, 6), (5, 6)},
# 'C': {(4, 5), (4, 7), (5, 8), (7, 8)},
# 'D': {(5, 6), (5, 8), (6, 9), (8, 9)}}
#%%
from collections import defaultdict
touching = defaultdict(list)
for name, edges in ordered_edges.items():
for edge in edges:
touching[edge].append(name)
touches = {edge: names
for edge, names in touching.items()
if len(names) > 1}
# = {(2, 5): ['A', 'B'],
# (4, 5): ['A', 'C'],
# (5, 6): ['B', 'D'],
# (5, 8): ['C', 'D']}
#%%
dont_merge = set([(4, 5), (23, 24)])
for edge, (name1, name2) in touches.items():
if (edge not in dont_merge
and ordered_edges[name1] and ordered_edges[name2]
#and check_angle(name1, name2)
):
# merge
ordered_edges[name1].update(ordered_edges[name2])
ordered_edges[name1].discard(edge) # that edge is merged away
ordered_edges[name2] = set() # gone
merged_ordered_edges = {}
for name, edges in ordered_edges.items():
if edges:
merged_ordered_edges[name] = sorted(edges)
edges.clear() # Only one name of shared object used
# = {'A': [(1, 2), (1, 4), (2, 3), (3, 6), (4, 5), (5, 6)],
# 'C': [(4, 5), (4, 7), (5, 6), (6, 9), (7, 8), (8, 9)]}
## You would then need a routine to change the ordered edges format
## back to your initial mesh_dict format that goes around the periphery
## (Or would you)?
#%%
def ordered_to_periphery(edges):
"""
In [124]: ordered_to_periphery([(1, 2), (1, 4), (2, 3), (3, 6), (4, 5), (5, 8), (6, 9), (8, 9)])
Out[124]: [(1, 2), (2, 3), (3, 6), (6, 9), (9, 8), (8, 5), (5, 4), (4, 1)]
"""
p = [edges.pop(0)] if edges else []
last = p[-1][-1] if p else None
while edges:
for n, (i, j) in enumerate(edges):
if i == last:
p.append((i, j))
last = j
edges.pop(n)
break
elif j == last:
p.append((j, i))
last = i
edges.pop(n)
break
return p
#%%
merged_mesh = {name: ordered_to_periphery(edges)
for name, edges in merged_ordered_edges.items()}
# = {'A': [(1, 2), (2, 3), (3, 6), (6, 5), (5, 4), (4, 1)],
# 'C': [(4, 5), (5, 6), (6, 9), (9, 8), (8, 7), (7, 4)]}
P.S. Any chance of a mention if you use this?
I have to write a very little Python program that checks whether some group of coordinates are all connected together (by a line, not diagonally). The next 2 pictures show what I mean. In the left picture all colored groups are cohesive, in the right picture not:
I've already made this piece of code, but it doesn't seem to work and I'm quite stuck, any ideas on how to fix this?
def cohesive(container):
co = container.pop()
container.add(co)
return connected(co, container)
def connected(co, container):
done = {co}
todo = set(container)
while len(neighbours(co, container, done)) > 0 and len(todo) > 0:
done = done.union(neighbours(co, container, done))
return len(done) == len(container)
def neighbours(co, container, done):
output = set()
for i in range(-1, 2):
if i != 0:
if (co[0] + i, co[1]) in container and (co[0] + i, co[1]) not in done:
output.add((co[0] + i, co[1]))
if (co[0], co[1] + i) in container and (co[0], co[1] + i) not in done:
output.add((co[0], co[1] + i))
return output
this is some reference material that should return True:
cohesive({(1, 2), (1, 3), (2, 2), (0, 3), (0, 4)})
and this should return False:
cohesive({(1, 2), (1, 4), (2, 2), (0, 3), (0, 4)})
Both tests work, but when I try to test it with different numbers the functions fail.
You can just take an element and attach its neighbors while it is possible.
def dist(A,B):return abs(A[0]-B[0]) + abs(A[1]-B[1])
def grow(K,E):return {M for M in E for N in K if dist(M,N)<=1}
def cohesive(E):
K={min(E)} # an element
L=grow(K,E)
while len(K)<len(L) : K,L=L,grow(L,E)
return len(L)==len(E)
grow(K,E) return the neighborhood of K.
In [1]: cohesive({(1, 2), (1, 3), (2, 2), (0, 3), (0, 4)})
Out[1]: True
In [2]: cohesive({(1, 2), (1, 4), (2, 2), (0, 3), (0, 4)})
Out[2]: False
Usually, to check if something is connected, you need to use disjoint set data structures, the more efficient variations include weighted quick union, weighted quick union with path compression.
Here's an implementation, http://algs4.cs.princeton.edu/15uf/WeightedQuickUnionUF.java.html which you can modify to your needs. Also, the implementation found in the book "The Design and Analysis of Computer Algorithms" by A. Aho, allows you to specify the name of the group that you add 2 connected elements to, so I think that's the modification you're looking for.(It just involves using 1 extra array which keeps track of group numbers).
As a side note, since disjoint sets usually apply to arrays, don't forget that you can represent an N by N matrix as an array of size N*N.
EDIT: just realized that it wasn't clear to me what you were asking at first, and I realized that you also mentioned that diagonal components aren't connected, in that case the algorithm is as follows:
0) Check if all elements refer to the same group.
1) Iterate through the array of pairs that represent coordinates in the matrix in question.
2) For each pair make a set of pairs that satisfies the following formula:
|entry.x - otherEntry.x| + |entry.y - otherEntry.y|=1.
'entry' refers to the element that the outer for loop is referring to.
3) Check if all of the sets overlap. That can be done by "unioning" the sets you're looking at, at the end if you get more than 1 set, then the elements are not cohesive.
The complexity is O(n^2 + n^2 * log(n)).
Example:
(0,4), (1,2), (1,4), (2,2), (2,3)
0) check that they are all in the same group:
all of them belong to group 5.
1) make sets:
set1: (0,4), (1,4)
set2: (1,2), (2,2)
set3: (0,4), (1,4) // here we suppose that sets are sorted, other than that it
should be (1,4), (0,4)
set4: (1,2), (2,2), (2,3)
set5: (2,2), (2,3)
2) check for overlap:
set1 overlaps with set3, so we get:
set1' : (0,4), (1,4)
set2 overlaps with set4 and set 5, so we get:
set2' : (1,2), (2,2), (2,3)
as you can see set1' and set2' don't overlap, hence you get 2 disjoint sets that are in the same group, so the answer is 'false'.
Note that this is inefficient, but I have no idea how to do it more efficiently, but this answers your question.
The logic in your connected function seems wrong. You make a todo variable, but then never change its contents. You always look for neighbours around the same starting point.
Try this code instead:
def connected(co, container):
done = {co}
todo = {co}
while len(todo) > 0:
co = todo.pop()
n = neighbours(co, container, done)
done = done.union(n)
todo = todo.union(n)
return len(done) == len(container)
todo is a set of all the points we are still to check.
done is a set of all the points we have found to be 4-connected to the starting point.
I'd tackle this problem differently... if you're looking for five exactly, that means:
Every coordinate in the line has to be neighbouring another coordinate in the line, because anything less means that coordinate is disconnected.
At least three of the coordinates have to be neighbouring another two or more coordinates in the line, because anything less and the groups will be disconnected.
Hence, you can just get the coordinate's neighbours and check whether both conditions are fulfilled.
Here is a basic solution:
def cells_are_connected(connections):
return all(c > 0 for c in connections)
def groups_are_connected(connections):
return len([1 for c in connections if c > 1]) > 2
def cohesive(coordinates):
connections = []
for x, y in coordinates:
neighbours = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
connections.append(len([1 for n in neighbours if n in coordinates]))
return cells_are_connected(connections) and groups_are_connected(connections)
print cohesive([(1, 2), (1, 3), (2, 2), (0, 3), (0, 4)]) # True
print cohesive([(1, 2), (1, 4), (2, 2), (0, 3), (0, 4)]) # False
No need for a general-case solution or union logic. :) Do note that it's specific to the five-in-a-line problem, however.
Python. I have two lists, same length. The idea is to build paired data (for regression analysis). I figured out loops and it look like this.
a=(1,3,5,7) #first list
b=(2,4,6,10) #second list
w=zip(a,b) #paired values from both lists
i=0
j=0
for each in w:
x= w[i]
for that in xrange(i,len(w)-1):
i+=1
print x, w[i]
j+=1
i=j
The output is as I was expected - first pair together with second, third.. so on, then second pair with the third, fourth.. and so on (skipping the combination between second pair and first pair, because it is kinda the same as combination of first and second pairs...)
(1, 2) (3, 4)
(1, 2) (5, 6)
(1, 2) (7, 10)
(3, 4) (5, 6)
(3, 4) (7, 10)
(5, 6) (7, 10) [..] and so on as I was expecting.
The question is - is there some other, shorter, optimized ways how to rewrite this code, maybe using itertools?
You can use itertools.combinations with itertools.izip:
>>> from itertools import izip, combinations
>>> for a, b in combinations(izip(a, b), 2):
print a, b
...
(1, 2) (3, 4)
(1, 2) (5, 6)
(1, 2) (7, 10)
(3, 4) (5, 6)
(3, 4) (7, 10)
(5, 6) (7, 10)
Yes: itertools.combinations
print list(itertools.combinations(w, 2))
You mention this in your question - I'm not sure why you wouldn't look in the docs before asking on StackOverflow.
I have a map, let's call it M, which contains data mapped through N dimensions.
# If it was a 2d map, I could iterate it thusly:
start, size = (10, 10), (3, 3)
for x in range(start[0], start[0]+size[0]):
for y in range(start[1], start[1]+size[1]):
M.get((x, y))
# A 3d map would add a for z in ... and access it thusly
M.get((x, y, z))
# And so on.
My question is: How do I make an iterator that can yield the correct iteration sequence? That is, given start, size = (10, 10), (3, 3) it would yield the 2-tuple sequence (10, 10), (10, 11), (10, 12), (11, 10), (11, 11) etc. And given start, size = (10, 10, 10), (3, 3, 3) it would yield the correct 3-tuple sequence.
Yeah, I tried myself, but my head exploded. Or I can't justify spending time figuring it out, even though it's fun. Take your pick :)
In Python 2.6+:
itertools.product(*[xrange(i, i+j) for i,j in zip(start, size)])
With do it your self generator expreessions:
start, size = (10, 10), (3, 3)
values2=((x+xd,y+yd)
for x,y in (start,)
for xr,yr in (size,)
for xd in range(xr)
for yd in range(yr))
for x,y in values2:
print x,y
start, size = (10, 10, 10), (3, 3, 3)
values3=((x+xd,y+yd, z+zd)
for x,y,z in (start,)
for xr,yr,zr in (size,)
for xd in range(xr)
for yd in range(yr)
for zd in range(zr))
for x,y,z in values3:
print x,y,z