1

I hope, this image can explain my problem better like following words. Let's say, the "shape" is represented as coordinates like:

shape = {(0,0), (0,1), (1,0), (1,1)}

I now want to find every possible combination of range(2, len(shape)+1) subshapes that are able to compose the shape. Rotated duplicates are not needed (like they are omitted on the image).

For now I started with the code below, which I think leads to a useful direction, but I have a very deep feeling, that there is a more elegant way out there instead of "iterating through all possible solutions" ;). Maybe someone has an idea or can push me in the right direction.

from itertools import combinations_with_replacement


# coordinates of "pixels": origin (0,0) is top left
shape = [(0,0),(0,1),(1,0),(1,1)]

def sums(a, b):
    return tuple(sum(x) for x in zip(a, b))

def combinations_pixels_per_subshape(shape: list, n_subshapes: int):
    # returns the possible combinations of n pixels per n_subshapes,
    # that add up to the pixels in given shape.  
    shape_pixels = len(shape)
    for c in combinations_with_replacement(range(1, shape_pixels), n_subshapes):
        if sum(c) == shape_pixels:
            yield(c)



# a shape can have as may subshapes as single pixels.
for nsub in range(2, len(shape) + 1):
    print(nsub, "subshapes")
    for comb in combinations_pixels_per_subshape(shape, nsub):
        print(comb) 
        for subpix in comb:
            print(subpix)
            
    break

The expected output (or one possible representation of it) could be - according to the image:

[[(0,0)], [(0,1)], [(1,0)], [(1,1)]] # every single pixel (=4 subshapes)
[[(0,0)], [(0,1), (1,0), (1,1)]] # a single pixel and a block of 3 (=2 subshapes)
[[(0,0),(1,0)],[(0,1),(1,1)]] # 2 blocks of 2 (=2 subshapes)
[[(0,0)], [(0,1)], [(1,0), (1,1)]] # 2 single pixel and one block of 2 (=3 subshapes)

the last line could just as well be [[(1,0)], [(1,1)], [(0,0), (0,1)]] or [[(0,0)], [(1,0)], [(0,1), (1,1)]] which would be the same shapes, but at different locations or rotated by 90°.

slapslash
  • 54
  • 5
  • 1
    I understand the images, but what is the expected output of the program? – trincot Apr 28 '23 at 10:17
  • sorry, I added a possible output, to make it somehow more clear. – slapslash Apr 28 '23 at 12:02
  • What about all the rotated version? What about the case of the both diagonals as shapes? – MrSmith42 Apr 28 '23 at 12:19
  • I would only need _a_ version, but if I would get all possible solutions (same shapes, but rotated), it would be fine too. Diagonals are evil and therefor unwanted! Sorry diagonal friends out there. – slapslash Apr 28 '23 at 12:48

1 Answers1

0

I think this is a famous LC/CP problem.

If you don't care about the generating rotated duplicates and don't care about recursive stack complexity, here's a straightforward solution: Playground link

def generate_subshapes(shape, n):
    if n == 1:
        return [[p] for p in shape]
    else:
        subshapes = []
        for i in range(len(shape)):
            for s in generate_subshapes(shape[i+1:], n-1):
                subshapes.append([shape[i]] + s)
        return subshapes

shape = [(0,0), (0,1), (1,0), (1,1)]
for n in range(2, len(shape)+1):
    print(n, "subshapes")
    subshapes = generate_subshapes(shape, n)
    for s in subshapes:
        print(s)

The output of which is:

2 subshapes
[(0, 0), (0, 1)]
[(0, 0), (1, 0)]
[(0, 0), (1, 1)]
[(0, 1), (1, 0)]
[(0, 1), (1, 1)]
[(1, 0), (1, 1)]
3 subshapes
[(0, 0), (0, 1), (1, 0)]
[(0, 0), (0, 1), (1, 1)]
[(0, 0), (1, 0), (1, 1)]
[(0, 1), (1, 0), (1, 1)]
4 subshapes
[(0, 0), (0, 1), (1, 0), (1, 1)]

To avoid generating rotated duplicates, you can add a check to skip subshapes that are rotations of previously generated subshapes.

Jishan Shaikh
  • 1,572
  • 2
  • 13
  • 31
  • Thank's for your suggestion, but that doesn't quite do, what i'm looking for. But I updated the question, to make it more clear. What is the famous LC/CP problem? can't find anything about it... – slapslash Apr 28 '23 at 13:13