2

So I am working on a UVA problem and I have 4 nested loops to iterate over a list of polygons (each polygon contains a list of points where each point contains an integer x and y to represent it's coordinates, i.e polygon[0] is a point which coordinates are polygon[0].x and polygon[0].y).

I am trying to reduce the number of for loops in the program in order to make it more efficient and have a lower runtime. My code is as follows:

for i in range(len(polygons)): # iterate over all the different polygons in the test case
    for j in range(i+1, len(polygons)): # iterate over all the different polygons in the test case but starting from the second, in order to make comparations between polygons i and j
        for a in range(len(polygons[i])):
            if (isInside(polygons[i][a].x, polygons[i][a].y, polygons[j])):
                union(i,j)
        for a in range(len(polygons[j])):
            if (isInside(polygons[j][a].x, polygons[j][a].y, polygons[i])):
                union(i,j)
        f = 1
        for a in range(len(polygons[i])): # iterate over all the different points in the polygon i
            for b in range(len(polygons[j])): # iterate over all the different points in the polygon j
                if (f!=0):
                    if(doIntersect(polygons[i][a], polygons[i][(a+1)%len(polygons[i])],polygons[j][b], polygons[j][(b+1)%len(polygons[j])])): # check if every single pair of line segments, each one made up of two points, intersect with each other
                        union(i,j) # the two line segments intersect so we join them by using union
                        f = 0

And I tried to make it more efficient by using itertools.product as follows:

def solve():
global polygons, p

ranges = [range(len(polygons)), range(1,len(polygons))]

for i, j in product(*ranges):
    for a in range(len(polygons[i])):
        if (isInside(polygons[i][a].x, polygons[i][a].y, polygons[j])):
            union(i,j)
    for a in range(len(polygons[j])):
        if (isInside(polygons[j][a].x, polygons[j][a].y, polygons[i])):
            union(i,j)
    f = 1
    ranges2 = [range(len(polygons[i])), range(len(polygons[j]))]
    for a,b in product(*ranges2):
        if (f!=0):
            if(doIntersect(polygons[i][a], polygons[i][(a+1)%len(polygons[i])],polygons[j][b], polygons[j][(b+1)%len(polygons[j])])): # check if every single pair of line segments, each one made up of two points, intersect with each other
                union(i,j) # the two line segments intersect so we join them by using union
                f = 0

Anyhow my code is having the same runtime in both cases, is there a way to reduce the number of nested loops for my algorithm?

Thanks in advance for any given help, much appreciated

1
  • You don't have a product for the outer loop, you are producing combinations. Commented May 11, 2017 at 17:11

1 Answer 1

4

Your two outer loops are creating combinations of lists; use the itertools.combinations() iterator for those. Your innermost double loop produces the carthesian product, so use the itertools.product() iterator.

Don't generate indices with range(), just loop directly over the polygon lists; useenumerate()` to add indices rather than make indices work the other way around.

To pair up sections, the pairwise() recipe from the itertools recipes section; that'll let you get all segments to work with. To circle round to the start again (pairing up the last coordinate with the first), just append a list with the first element to the end.

Once you get rid of nested loops, you can use break to end them rather than use a flag variable.

from itertools import combinations, product

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

for (i, a_poly), (j, b_poly) in combinations(enumerate(polygons), 2):
    for a in a_poly:
        if isInside(a.x, a.y, b_poly):
            union(i, j)
    for b in b_poly:
        if isInside(b.x, b.y, a_poly):
            union(j, i)

    # attach the first element at the end so you go 'round'
    a_segments = pairwise(a_poly + a_poly[:1])
    b_segments = pairwise(b_poly + b_poly[:1])
    for a_seg, b_seg in product(a_segments, b_segments):
        if doIntersect(*a_seg, *b_seg):
            union(i,j)
            break

In fact, once you have determined something is a union, you don't have to continue with the rest of the tests. You could use the any() function to stop testing the isInside() and doIntersect functions early:

for (i, a_poly), (j, b_poly) in combinations(enumerate(polygons), 2):
    if any(isInside(a.x, a.y, b_poly) for a in a_poly):
        union(i, j)
        break  # union found, no need to look further

    for any(isInside(b.x, b.y, a_poly) for b in b_poly):
        union(i, j)
        break  # union found, no need to look further

    # attach the first element at the end so you go 'round'
    a_segments = pairwise(a_poly + a_poly[:1])
    b_segments = pairwise(b_poly + b_poly[:1])
    if any(doIntersect(*a_seg, *b_seg) 
           for a_seg, b_seg in product(a_segments, b_segments)):
        union(i,j)

This is not only far more readable now, it should also be more efficient!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.