pythonloopslambdalist-comprehensionlist-manipulation

List of coordinates filtering


In python I have a list of contours (each holding x, y, w, and h integers), I need to somehow iterate over the list and check, if the current x + w is greater than the last x + w by less than 3 (which mean by 1 or 2) remove the current one, otherwise keep going.

I have this for now:

contours_v2 = [[551, 0, 2, 1], [491, 0, 1, 1], [484, 0, 6, 1], [482, 0, 1, 1], [480, 0, 1, 1], [400, 0, 6, 1], [321, 0, 6, 1], [319, 0, 1, 1], [238, 0, 1, 1], [234, 0, 3, 1], [229, 0, 4, 1], [227, 0, 1, 1], [225, 0, 1, 1], [223, 0, 1, 1], [142, 0, 1, 1], [132, 0, 6, 1], [130, 0, 1, 1], [0, 0, 7, 1]]
last_x_w = contours_v2[0][0] + contours_v2[0][2] # Initialize with the first contour's x + w value
i = 1 # Start with the second contour
while i < len(contours_v2):
    current_x_w = contours_v2[i][0] + contours_v2[i][2]
    if abs(last_x_w - current_x_w) < 4:
        contours_v2.pop(i)
    else:
        last_x_w = current_x_w
        i += 1

I Need a few things:

  1. It's not working on all of the cases, sometimes I still have ones that should have been poped.
  2. I was wondering if there is a way to do it with lambda or list comprehension

Updates:

If I move the i += 1 into the else statement, the resulting list will be (input list was the example I provided):

# As you can see, the item didn't pop and should have popped is
# the one with x = 229, its next one is x = 227
# contours_v2
[[551, 0, 2, 1], [491, 0, 1, 1], [482, 0, 1, 1], [400, 0, 6, 1], [321, 0, 6, 1], [319, 0, 1, 1], [238, 0, 1, 1], [229, 0, 4, 1], [227, 0, 1, 1], [223, 0, 1, 1], [142, 0, 1, 1], [132, 0, 6, 1], [130, 0, 1, 1], [0, 0, 7, 1]]

Solution

  • One way to write shorter (but not necessarily faster) code is

    from functools import reduce
    
    contours_v2 = [[551, 0, 2, 1], [491, 0, 1, 1], [484, 0, 6, 1], [482, 0, 1, 1], [480, 0, 1, 1], [400, 0, 6, 1], [321, 0, 6, 1], [319, 0, 1, 1], [238, 0, 1, 1], [234, 0, 3, 1], [229, 0, 4, 1], [227, 0, 1, 1], [225, 0, 1, 1], [223, 0, 1, 1], [142, 0, 1, 1], [132, 0, 6, 1], [130, 0, 1, 1], [0, 0, 7, 1]]
    
    def x_w(c):
        return c[0] + c[2]
    
    r = reduce(lambda x, y: x if abs(x_w(x[-1]) - x_w(y)) < 4 else x + [y],
            contours_v2[1:], [contours_v2[0]])
    
    print(r)
    

    The initializer of reduce is a list with the first item of contours_v2, then for each item of the rest its distance to the last item of the list is checked and by the if-expression either the unmodified list is returned by the lambda or a new list with the new item appended is returned.

    The code does some things which may be slower than a conventional loop because it may have to repeatedly calculate c[0] + c[2] for the same c and it uses something equivalent to x = x + [y] which is slower than x.append(y).