Algorithm for polygon triangulation - python-3.x

I have written the following code for the generation of all "triangulations" of a regular or convex polygon:
def getTriangles(points,i,j):
print('i={}, j={}'.format(i,j))
ee = []
if j-i<2:
return []
if j-i==2:
return [[[i,i+1,j]]]
for k in range(i+1,j):
print(' k={}'.format(k))
e1= getTriangles(points,i,k)
e2 = getTriangles(points,k,j)
for x in e1:
for y in e2:
e = [[i,k,j]]
e.extend(x)
e.extend(y)
ee.append(e)
if len(e1)==0:
for y in e2:
e = [[i,k,j]]
e.extend(y)
ee.append(e)
if len(e2)==0:
for a in e1:
e = [[i,k,j]]
e.extend(x)
ee.append(e)
print(' e1={}, e2={}, ee={}'.format(e1,e2,ee))
return ee
n=5
tr = getTriangles(range(1,n+1),0,n-1)
print()
print(tr)
print(len(tr))
For n=3,4 it is correct, and in general "navigates" through the right number of possible triangulations (that is the Catalan number) for n=3,4,5,6,7,8, but the triangulations are not unique. here the formatted output for n=5, consisting of a list of triangles (e.g. three vertices in [0,1,2,3,4]):
[[[0, 1, 4], [1, 2, 4], [2, 3, 4]],
[[0, 1, 4], [1, 3, 4], [1, 2, 3]],
[[0, 2, 4], [0, 1, 2], [2, 3, 4]],
[[0, 3, 4], [0, 2, 3], [0, 1, 2]],
[[0, 3, 4], [0, 2, 3], [0, 1, 2]]]
as you can see the last two are equal. Where is the error?
Intuitively the code is more complex than needed.
EDIT As you can see I'm not in bad company for this error: here is Robert Sedgewick, a computer science professor at Princeton University and in the background you see that the n=5 is correct but for n=6 there are double ;-)

The following code seams to work. The change is in the middle. The algorithm fix the edge [i,j] and k moves from i+1 to j-1. The triangle ijk is fixed and split the polygon in three sub-polygons: itself, the polygon i...k, and the polygon, k..j. If k=i+1 or k=j-1, one of the two polygon is degenerate (empty):
def getTriangles(points,i,j):
print('i={}, j={}'.format(i,j))
ee = []
if j-i<2:
return []
if j-i==2:
return [[[i,i+1,j]]]
for k in range(i+1,j): # k is the vertex such that the triangle ikj split the polygon 1j in 2 subspace plus the triangle ikj
print(' k={}'.format(k))
e1= getTriangles(points,i,k)
e2 = getTriangles(points,k,j)
if k==i+1:
for y in e2:
e = [[i,k,j]]
e.extend(y)
ee.append(e)
elif k==j-+1:
for x in e1:
e = [[i,k,j]]
e.extend(x)
ee.append(e)
else:
for x in e1:
for y in e2:
e = [[i,k,j]]
e.extend(x)
e.extend(y)
ee.append(e)
print(' e1={}, e2={}, ee={}'.format(e1,e2,ee))
return ee
n=5
tr = getTriangles(range(1,n+1),0,n-1)
print()
print(tr)
print(len(tr))

Here's a more Pythonic version of the above:
def genTriangles(i, j):
if j - i < 2:
yield []
return
if j - i == 2:
yield [(i, i+1, j)]
return
for k in range(i + 1, j):
for x in genTriangles(i, k):
for y in genTriangles(k, j):
yield x + y + [(i, k, j)]
n = 5
for k, tr in enumerate(genTriangles(0, n - 1), 1):
print(k, tr)
1 [(2, 3, 4), (1, 2, 4), (0, 1, 4)]
2 [(1, 2, 3), (1, 3, 4), (0, 1, 4)]
3 [(0, 1, 2), (2, 3, 4), (0, 2, 4)]
4 [(1, 2, 3), (0, 1, 3), (0, 3, 4)]
5 [(0, 1, 2), (0, 2, 3), (0, 3, 4)]

Related

Remove non-conjugates from complex numbers list

I have two lists, one contains the real part of imaginary numbers, the other contains the imaginary part of the same numbers. I want to remove from both lists the imaginary numbers that do not have a conjugate.
For example, the following lists x = [3, 4, 2, 7, 4] and y = [2, -1, 0, 6, 1] represent the numbers :
3 + 2j <- no conjugate (to remove)
4 - 1j <- conjugate (to keep)
2 + 0j <- real (to keep)
4 + 1j <- conjugate (to keep)
7 + 6j <- no conjugate (to remove)
The expected result is the following :
new_x = [4, 2, 4]
new_y = [-1, 0, 1]
Any idea how i can achieve this ? Thanks
This script will find complex conjugates from lists x and y:
x = [3, 4, 2, 7, 4]
y = [2, -1, 0, 6, 1]
tmp = {}
for r, i in zip(x, y):
tmp.setdefault(i, set()).add(r)
x_out, y_out = [], []
for r, i in zip(x, y):
if i==0 or r in tmp.get(-i, []):
x_out.append(r)
y_out.append(i)
print(x_out)
print(y_out)
Prints:
[4, 2, 4]
[-1, 0, 1]

How to generate permutations by decreasing cycles?

Here are two related SO questions 1 2 that helped me formulate my preliminary solution.
The reason for wanting to do this is to feed permutations by edit distance into a Damerau-Levenshtein NFA; the number of permutations grows fast, so it's a good idea to delay (N-C) cycle N permutations candidates until (N-C) iterations of the NFA.
I've only studied engineering math up to Differential Equations and Discrete Mathematics, so I lack the foundation to approach this task from a formal perspective. If anyone can provide reference materials to help me understand this problem properly, I would appreciate that!
Through brief empirical analysis, I've noticed that I can generate the swaps for all C cycle N permutations with this procedure:
Generate all 2-combinations of N elements (combs)
Subdivide combs into arrays where the smallest element of each 2-combination is the same (ncombs)
Generate the cartesian products of the (N-C)-combinations of ncombs (pcombs)
Sum pcombs to get a list of the swaps that will generate all C cycle N permutations (swaps)
The code is here.
My Python is a bit rusty, so helpful advice about the code is appreciated (I have the feeling that lines 17, 20, and 21 should be combined. I'm not sure if I should be making lists of the results of itertools.(combinations|product). I don't know why line 10 can't be ncombs += ... instead of ncombs.append(...)).
My primary question is how to solve this question properly. I did the rounds on my own due diligence by finding a solution, but I am sure there's a better way. I've also only verified my solution for N=3 and N=4, is it really correct?
The ideal solution would be functionally identical to heap's algorithm, except it would generate the permutations in decreasing cycle order (by the minimum number of swaps to generate the permutation, increasing).
This is far from Heap's efficiency, but it does produce only the necessary cycle combinations restricted by the desired number of cycles, k, in the permutation. We use the partitions of k to create all combinations of cycles for each partition. Enumerating the actual permutations is just a cartesian product of applying each cycle n-1 times, where n is the cycle length.
Recursive Python 3 code:
from math import ceil
def partitions(N, K, high=float('inf')):
if K == 1:
return [[N]]
result = []
low = ceil(N / K)
high = min(high, N-K+1)
for k in range(high, low - 1, -1):
for sfx in partitions(N-k, K - 1, k):
result.append([k] + sfx)
return result
print("partitions(10, 3):\n%s\n" % partitions(10, 3))
def combs(ns, subs):
def g(i, _subs):
if i == len(ns):
return [tuple(tuple(x) for x in _subs)]
res = []
cardinalities = set()
def h(j):
temp = [x[:] for x in _subs]
temp[j].append(ns[i])
res.extend(g(i + 1, temp))
for j in range(len(subs)):
if not _subs[j] and not subs[j] in cardinalities:
h(j)
cardinalities.add(subs[j])
elif _subs[j] and len(_subs[j]) < subs[j]:
h(j)
return res
_subs = [[] for x in subs]
return g(0, _subs)
A = [1,2,3,4]
ns = [2, 2]
print("combs(%s, %s):\n%s\n" % (A, ns, combs(A, ns)))
A = [0,1,2,3,4,5,6,7,8,9,10,11]
ns = [3, 3, 3, 3]
print("num combs(%s, %s):\n%s\n" % (A, ns, len(combs(A, ns))))
def apply_cycle(A, cycle):
n = len(cycle)
last = A[ cycle[n-1] ]
for i in range(n-1, 0, -1):
A[ cycle[i] ] = A[ cycle[i-1] ]
A[ cycle[0] ] = last
def permutations_by_cycle_count(n, num_cycles):
arr = [x for x in range(n)]
cycle_combs = []
for partition in partitions(n, num_cycles):
cycle_combs.extend(combs(arr, partition))
result = {}
def f(A, cycle_comb, i):
if i == len(cycle_comb):
result[cycle_comb].append(A)
return
if len(cycle_comb[i]) == 1:
f(A[:], cycle_comb, i+1)
for k in range(1, len(cycle_comb[i])):
apply_cycle(A, cycle_comb[i])
f(A[:], cycle_comb, i+1)
apply_cycle(A, cycle_comb[i])
for cycle_comb in cycle_combs:
result[cycle_comb] = []
f(arr, cycle_comb, 0)
return result
result = permutations_by_cycle_count(4, 2)
print("permutations_by_cycle_count(4, 2):\n")
for e in result:
print("%s: %s\n" % (e, result[e]))
Output:
partitions(10, 3):
[[8, 1, 1], [7, 2, 1], [6, 3, 1], [6, 2, 2], [5, 4, 1], [5, 3, 2], [4, 4, 2], [4, 3, 3]]
# These are the cycle combinations
combs([1, 2, 3, 4], [2, 2]):
[((1, 2), (3, 4)), ((1, 3), (2, 4)), ((1, 4), (2, 3))]
num combs([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [3, 3, 3, 3]):
15400
permutations_by_cycle_count(4, 2):
((0, 1, 2), (3,)): [[2, 0, 1, 3], [1, 2, 0, 3]]
((0, 1, 3), (2,)): [[3, 0, 2, 1], [1, 3, 2, 0]]
((0, 2, 3), (1,)): [[3, 1, 0, 2], [2, 1, 3, 0]]
((1, 2, 3), (0,)): [[0, 3, 1, 2], [0, 2, 3, 1]]
((0, 1), (2, 3)): [[1, 0, 3, 2]]
((0, 2), (1, 3)): [[2, 3, 0, 1]]
((0, 3), (1, 2)): [[3, 2, 1, 0]]

How to fix tribonacci series function when using generators

Following is my approach to return the n element in Tribonacci series
def tri(n,seq = [1, 1, 1]):
for i in range(n-2):
seq = seq[1:] + [sum(seq)]
return seq[-1]
I get the correct result when passing argument through print().
print(tri(10))
Output : 193
However, when using generator(using repl.it), I get error of can only concatenate tuple (not"list") to tuple
I am using below for generator
def tri_generator():
for i in range(1000):
yield (i, (1, 1, 1))
yield (i, (1, 0, 1))
yield (i, (1, 2, 3))
Not sure what I am missing? Any help is appreciated.
Here's a simple generator (you can clean up the code as you may like):
def tri_generator():
i = 0
seq = [1, 1, 1]
while True:
seq = [seq[1], seq[2], seq[0] + seq[1] + seq[2]]
yield i, seq
i += 1
n = 10
xx = tri_generator()
for i in range(n - 2):
print(next(xx))
## Output:
## (0, [1, 1, 3])
## (1, [1, 3, 5])
## (2, [3, 5, 9])
## (3, [5, 9, 17])
## (4, [9, 17, 31])
## (5, [17, 31, 57])
## (6, [31, 57, 105])
## (7, [57, 105, 193])

Compute distance metric in a complete graph

Suppose I have an array, namely Map. Map[i][j] means the distance between area i and area j. Under this definition, we get:
a) Map[i][i] always equals 0.
b) Map[i][k] <= Map[i][j] + Map[j][k] for all i,j,k
I want to build a function func(Map,k) returning a metric D, while D[i][j] is the shortest distance of a route from area i to area j, and this route should pass through at least k different area.
This is my python code to do so:
def func(Map,k):
n=len(Map)
D_temp = [list(x) for x in Map]
D = [list(x) for x in Map]
for m in range(k - 1):
for i in range(n):
for j in range(n):
tmp = [D[i][x] + Map[x][j] for x in range(n) if x != i and x != j]
D_temp[i][j] = min(tmp)
D = [list(x) for x in D_temp]
return D
func([[0, 2, 3], [2, 0, 1], [3, 1, 0]],2)
return a distance metric D which equals [[4, 4, 3], [4, 2, 5], [3, 5, 2]]
D[0][0] equals 4, because the shortest route from area0 to area0 which pass through at least 2 area is {area0-->area1-->area0}, and the distance of the route is Map[0][1]+Map[1][0]=2+2=4
Wanted to know what would be the best way to do that?
You can use the A* algorithm for this, using Map[i][j] as the heuristic for the minimum remaining distance to the target node (assuming that, as you said, Map[i][j] <= Map[i][x] + Map[x][j] for all i,j,x). The only difference to a regular A* would be that you only accept paths if they have a minimum length of k.
import heapq
def min_path(Map, k, i, j):
heap = [(0, 0, i, [])]
while heap:
_, cost, cur, path = heapq.heappop(heap)
if cur == j and len(path) >= k:
return cost
for other in range(len(Map)):
if other != cur:
c = cost + Map[cur][other]
heapq.heappush(heap, (c + Map[other][j], c, other, path + [other]))
Change your func to return a list comprehension using this min_path accordingly.
def func(Map, k):
n = len(Map)
return [[min_path(Map, k, i, j) for i in range(n)] for j in range(n)]
res = func([[0, 2, 3], [2, 0, 1], [3, 1, 0]], 2)
This gives me the result [[4, 4, 3], [4, 2, 3], [3, 3, 2]] for len(path) >= k, or [[4, 4, 3], [4, 2, 5], [3, 5, 2]] for len(path) == k.

Elements in a list are overwritten

I tried to program a function which creates the linear span of a list of independent vectors, but it seems that the last calculated vector overwrites all other elements. I'd be nice if someone could help me fixing it.
def span_generator(liste,n):
"""function to generate the span of a list of linear independent
vectors(in liste) in the n-dimensional vectorspace of a finite
field with characteristic 2, returns a list of all elements which
lie inside the span"""
results=[]
blank=[]
for i in range(n):
blank.append(0)
a=blank
if len(liste)>1:
listenwert=liste[-1]
liste.pop(-1)
values=span_generator(liste,n)
for i in range(2):
for j in range(len(values)):
for k in range(n):
a[k]=(i*listenwert[k]+values[j][k])%2
results.append(a)
else:
for i in range(2):
for j in range(n):
a[j]=(i*liste[0][j])
results.append(a)
print(results)
return results
print(span_generator([[1,0],[0,1]],2)) gives following results
[[1, 0], [1, 0]]
[[1, 1], [1, 1], [1, 1], [1, 1]]
[[1, 1], [1, 1], [1, 1], [1, 1]]
instead of the expected: [[0,0],[1,0],[0,1],[1,1]]
Edit: I tried to simplify the program with itertools.product, but it didn't solve the problem.
def span_generator(liste):
n=len(liste[0])
results=[]
coeff=list(itertools.product(range(2), repeat=n))
blank=[]
for i in range(n):
blank.append(0)
for i in range(len(coeff)):
a=blank
for j in range(len(coeff[0])):
for k in range(n):
a[k]=(a[k]+coeff[i][j]*liste[j][k])%2
results.append(a)
return results
Output: span_generator([[0,1],[1,0]])
[[0, 0], [0, 0], [0, 0], [0, 0]]
But it should give [[0,0],[0,1],[1,0],[1,1]]
Another example: span_generator([[0,1,1],[1,1,0]]) should give [[0,0,0],[0,1,1],[1,1,0],[1,0,1]] (2=0 since i'm calculating modulo 2)
Coefficients
You can use itertools.product to generate the coefficients:
n = len(liste[0])
coefficients = itertools.product(range(2), repeat=len(liste))
yields an iterator with this content:
[(0, 0), (0, 1), (1, 0), (1, 1)]
Linear combinations
You can then selectively multiply the results with the transpose of your liste (list(zip(*liste)))
for coeff in coefficients:
yield [sum((a * c) for a, c in zip(transpose[i], coeff)) for i in range(n)]
which take for each dimensionality (for i in range(n)) the sum of the products
def span_generator3(liste):
n = len(liste[0])
transpose = list(zip(*liste))
coefficients = itertools.product(range(2), repeat=len(liste))
for coeff in coefficients:
yield [sum((a * c) for a, c in zip(transpose[i], coeff)) % 2 for i in range(n)]
this produces an iterator. If you want the result in a list-form, just can list() on the iterator
Result
list(span_generator3([[1,2],[4,8]]))
output:
[[0, 0], [4, 8], [1, 2], [5, 10]]
Higher dimensions
list(sorted(span_generator3([[1,2, 4],[8, 16, 32], [64, 128, 256]])))
output:
[[0, 0, 0],
[1, 2, 4],
[8, 16, 32],
[9, 18, 36],
[64, 128, 256],
[65, 130, 260],
[72, 144, 288],
[73, 146, 292]]
Modulo 2
If you want the result modulo 2, that's just adding 2 characters in the right place
def span_generator3_mod2(liste):
n = len(liste[0])
transpose = list(zip(*liste))
coefficients = itertools.product(range(2), repeat=len(liste))
# print(list(itertools.product(range(2), repeat=len(liste))))
for coeff in coefficients:
yield [sum((a * c) for a, c in zip(transpose[i], coeff)) % 2 for i in range(n)]
list(span_generator3_mod2([[0,1,1],[1,1,0]])) gives
[[0, 0, 0], [1, 1, 0], [0, 1, 1], [1, 0, 1]]

Resources