[Question]
  [
## Rectangle covers
Suppose you have a matrix of bits, for example the following.
```
1 1 0 0 0 1 1 0
1 1 1 1 0 1 1 1
0 1 1 1 0 1 1 1
1 1 0 1 1 1 1 0
1 1 0 1 1 1 0 1
```
We would like to find a *rectangle cover* for this matrix.
It is a set of rectangular subsets of the matrix that don't contain any 0s, but together contain all the 1s.
The subsets are not required to be disjoint.
Here is an example of a rectangle cover for the above matrix.
```
+----+         +----+
|1  1| 0  0  0 |1  1| 0
|    |         |    |
|  +-|-----+   |    |+-+
|1 |1| 1  1| 0 |1  1||1|
+----+     |   |    || |
   |       |   |    || |
 0 |1  1  1| 0 |1  1||1|
   +-------+   |    |+-+
+----+   +-----|-+  |
|1  1| 0 |1  1 |1| 1| 0
|    |   |     +----+
|    |   |       |   +-+
|1  1| 0 |1  1  1| 0 |1|
+----+   +-------+   +-+
```
The number of rectangles in this cover is 7.
## The task
Your input is a rectangular matrix of bits, taken in any reasonable format.
You can assume it contains at least one 1.
Your output is the minimum number of rectangles in a rectangle cover of the matrix.
The lowest byte count wins.
Standard [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'") rules apply.
## Test cases
```
[[1]] -> 1
[[1,1]] -> 1
[[1],[1]] -> 1
[[1,0,1]] -> 2
[[1,0],[0,0]] -> 1
[[1,0],[0,1]] -> 2
[[1,0],[1,1]] -> 2
[[1,1,1],[1,0,1]] -> 3
[[0,1,0],[1,1,1],[0,1,0]] -> 2
[[1,1,1],[1,0,1],[1,1,1]] -> 4
[[1,1,0],[1,1,1],[0,1,1]] -> 2
[[1,0,1,0],[1,1,1,1],[1,0,1,0]] -> 3
[[1,1,1,0],[1,0,1,0],[1,1,1,1],[0,0,1,0]] -> 4
[[1,1,1,0],[1,0,1,0],[1,1,1,1],[0,0,1,1]] -> 5
[[1,1,1,0],[1,0,1,0],[1,1,1,1],[0,1,1,1]] -> 4
[[1,1,0,0],[1,1,1,0],[0,1,1,1],[0,0,1,1]] -> 3
[[0,1,0,0],[0,1,1,1],[1,1,1,0],[0,0,1,0]] -> 4
[[0,0,1,0,0],[0,1,1,1,0],[1,1,1,1,1],[0,1,1,1,0],[0,0,1,0,0]] -> 3
```
  
[Answer]
# [Python 2](https://docs.python.org/2/), 318 315 271 bytes
Mr.Xcoder, ovs and Jonathan Frech saved a lot of bytes
```
p=range
def f(x,i=0,j=-1):z=len(x[0]);j+=1;i+=j/z;j%=z;return i<len(x)and(x[i][j]and-~min([f([[v,v[:j]+[2]*(r-j)+v[r:]][i<=y<=e]for y,v in enumerate(x)],i,j)for e in p(i,len(x))for r in p(j+1,z+1)if all(all(k[j:r])for k in x[i:e+1])]+[f(x,i,j)-1]*(x[i][j]>1))or f(x,i,j))
```
[Try it online!](https://tio.run/##lZLBbsIwDIbP61NESJOa1WgNsEshe4hdI2vqRrollIDSgoDDXp0laYFSNmk7tIr9f/7ttF7v68@VGR2Pa25z8yGjuSxIEe9A8RQ0HzKaHXgpTbwTKdKpTjibqoTrx8NU3/PD1Mp6Yw1Rs8DQ3MwdqVBodMfh11KZWBSxEFvYikxjIkb4ENuhpslW2AxRqBnfz7jEYmXJHrZEGSLNZiltXktniKBAUy9KL61jBU2nkLNNTicMDgmjqiB5Wcb@WQidWQzQwkNuqEwmDKkbIVzPuQ6Zm6Wd9plR6tiTRI@1rOrX97ySFSciuhNCMERgCM0ZriKEnpoGfXSJHZG69xUTcrcc6@V8DGfPcZt30YkOehP/VnfiECYdvV/fm6VLXLxCl3G3S0vd8umZn/yD91M8/ZH/8VYdKu1QXf/LV0zPFS3X7dHtcvqHLe89Iowiv2HLvLZqB1ZWm7L223ZZHpGxMWYRWVtlajJ4CUg2ALdpTRUFMqg@V5tyTt7kgABpXFzWOTuuoY7f "Python 2 – Try It Online")
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), ~~25~~ 24 [bytes](https://github.com/DennisMitchell/jelly/wiki/Code-page)
```
FJ‘ṁ×⁸ẆZ€Ẇ€ẎŒPFQP$$ÐṀL€Ṃ
```
**[Try it online!](https://tio.run/##y0rNyan8/9/N61HDjIc7Gw9Pf9S44@GutqhHTWuAFJjsOzopwC0wQEXl8ISHOxt8QGI7m/7//x8dbagDhLE6QNoASMcCAA "Jelly – Try It Online")** A typical golf-complexity solution, don't even bother with larger test cases, they will time out (the power set of all possible rectangles is inspected\*)
### How?
Forms all the possible rectangles that may be made. Takes the power-set of those rectangles and inspects them only keeping those sets which both contain no zeros and contain each of the ones at least once.
To achieve the "keeping those sets which both contain no zeros and contain each of the ones at least once" part the code first coerces the ones to a set of distinct integers greater than one, leaving the zeros as they are so that it becomes "finding the maxima of the product of the unique values".
```
FJ‘ṁ×⁸ẆZ€Ẇ€ẎŒPFQP$$ÐṀL€Ṃ - Link: list of lists of ones and zeros, M
F                        - flatten M into a single list
 J                       - range of length = [1,2,3,...,len(flattened(M))]
  ‘                      - increment       = [2,3,4,...,len(flattened(M))+1]
   ṁ                     - mould like M - reshapes it just like M again
     ⁸                   - chain's left argument, M
    ×                    - multiply (vectorises) - unique integers > 1 at M's 1s and 0s at M's 0s
      Ẇ                  - non-empty sublists - i.e. row selections
       Z€                - transpose €ach
         Ẇ€              - non-empty sublists of €ach - column selections of those
           Ẏ             - tighten - a flat list of all of the rectangles
            ŒP           - power-set - all possible selections of rectangles
                   ÐṀ    - filter keep those for which the following is maximal:
                  $      -   last two links as a monad:
              F          -     flatten
                 $       -     last two links as a monad:
               Q         -       de-duplicate
                P        -       product
                     L€  - length of €ach - # of rectangles used by each full-cover
                       Ṃ - minimum
```
---
\* For an **n** by **m** matrix that's **ways(n,m) = 2^(T(n)×T(m))**, so...  
**ways(3,2) = 2^((3+2+1)×(2+1)) = 2^18 = 262,144** (the TIO link)  
**ways(3,3) = 2^((3+2+1)×(3+2+1)) = 2^36 = 68,719,476,736**  
**ways(3,4) = 2^((3+2+1)×(4+3+2+1)) = 2^60 = 1,152,921,504,606,846,976**  
**ways(5,5) = 2^225 ~= 5.4e+67** (the largest test case)  
**ways(8,5) = 2^540 ~= 3.6e+162** (the example)
[Answer]
# JavaScript, 434 bytes
Code:
```
for(_='),d=...-1||(,Ad<=a,u[o][n]=d,    =0,(e,r,C,m))&&()=>.map((h((A,n,on<e|o<r|n>C|o>mf=u=>(q(s=(e>C[e,C]=[C,e]r>m[r,m]=[m,r]lk=1,k&=!!A)kl|=&1,=2k&lh=f=>uA,$ABf(B,$))))(($,Bae=r=C=m=,d=to-Bt=n$&n>$e   C=nn+1~ee   C=ttn-$t=oB&o>Br    m=oo+1~rr   m=tq+=sg=[],h((ca||g.push(c)gigb,j(p=1,q+=i<j&&s(b)q)';G=/[-]/.exec(_);)with(_.split(G))_=join(shift());eval(_)
```
Hexdump (because of unprintable characters):
```
66 6F 72 28 5F 3D 27 29 2C 13 13 64 3D 12 2E 2E 2E 11 2D 31 10 7C 7C 28 0F 2C 41 0F 64 3C 3D 0E 61 2C 0C 75 5B 6F 5D 5B 6E 5D 0B 3D 64 2C 09 3D 30 2C 08 28 65 2C 72 2C 43 2C 6D 07 29 29 13 06 26 26 28 05 29 3D 3E 04 2E 6D 61 70 28 28 03 68 28 28 41 2C 6E 2C 6F 04 02 02 6E 3C 65 7C 6F 3C 72 7C 6E 3E 43 7C 6F 3E 6D 0F 01 66 3D 75 3D 3E 28 71 08 28 73 3D 07 04 28 65 3E 43 05 5B 65 2C 43 5D 3D 5B 43 2C 65 5D 13 72 3E 6D 05 5B 72 2C 6D 5D 3D 5B 6D 2C 72 5D 13 6C 08 6B 3D 31 2C 01 6B 26 3D 21 21 41 29 13 6B 05 01 6C 7C 3D 0B 26 31 2C 0B 3D 32 06 6B 26 6C 13 68 3D 66 3D 3E 75 03 41 2C 24 04 41 03 0C 42 04 66 28 0C 42 2C 24 29 29 29 29 28 28 0C 24 2C 42 04 61 10 0F 65 3D 72 3D 43 3D 6D 3D 10 2C 64 3D 74 08 02 6F 2D 42 0F 74 3D 6E 0E 24 26 6E 3E 24 05 65 09 43 3D 6E 10 12 6E 2B 31 06 7E 65 0F 65 09 43 3D 74 12 74 08 02 6E 2D 24 0F 74 3D 6F 0E 42 26 6F 3E 42 05 72 09 6D 3D 6F 10 12 6F 2B 31 06 7E 72 0F 72 09 6D 3D 74 13 71 2B 3D 73 07 06 67 3D 5B 5D 2C 68 28 28 0C 11 63 04 61 10 7C 7C 67 2E 70 75 73 68 28 63 29 13 67 03 0C 69 04 67 03 62 2C 6A 04 28 70 3D 31 2C 71 2B 3D 69 3C 6A 26 26 73 28 11 0C 11 62 29 06 71 29 27 3B 47 3D 2F 5B 01 2D 13 5D 2F 2E 65 78 65 63 28 5F 29 3B 29 77 69 74 68 28 5F 2E 73 70 6C 69 74 28 47 29 29 5F 3D 6A 6F 69 6E 28 73 68 69 66 74 28 29 29 3B 65 76 61 6C 28 5F 29
```
[Try it online!](https://tio.run/##lZPfjqIwFMajgn9WIiovsJMQps0cGdlbPSTCxTwEIQ4jVVGhCjizF2Rf3S2zuqK7F7u9aM/X8ztfT9N0E7wH2SKN9vko4SE7nZY8JXN8pKBpIQ5N0xyMrH5REBVmajjFXgDK0eO@l/hdDKGDY2gTBim4ELco1ZqGQWSKtmTGwZ6QxpqQGSTApXo9mbKCT9Misd2C27FaW@IRbXJokwxbEmG2K3sMXB89F5ivpXYseynEQseQ@tquvUULalsDHx5mVNvKtV2BXcOCLn5rbo2dtsYl2sfGDHRp1lAcaUkUB3QqBiGKDo4U9FWGKboYYx9CzNt1PnLUHJOebiS2LrOOi0l/mDxZzR9MLVU@FFAy0gXEe47BbUdOOzHy/pCXUKqWKtcOT5i1miv0fBD3VQYLcVRRrMz9MVuTBdVWDSWSVo032EhkLy4h@Gi6MYyMDJTBG20e6OPkBZ@92kjzn032nS3InE7oR5SvydzM9rsoJy@UznHDo4Rk62iZE0on7D3YCfC0T/mCZZmZ5SE/5uZHGuWMeF88z/J9KBe4BD5c98ZwDcX@WMw38iZ7tYAyhGq9CC7MZ@qX/gt9QX6n7quqvV2TV4cb2zPwJzr@X9T6N/S@@QowrgB3ruduqkjVuep9eYczL8r9z18UoL0kAaVm@fyvX1/p6Sc)
It is not very golfy, but at least it works very fast. All test cases can be computed in few milliseconds.
## Ungolfed
```
f=mat=>(
  iterate=f=>mat.map((A,x)=>A.map((a,y)=>f(a,y,x))),
  fill=(x1,y1,x2,y2)=>(
    x1>x2&&([x1,x2]=[x2,x1]),
    y1>y2&&([y1,y2]=[y2,y1]),
    isFilled=0,
    canBeFilled=1,
    iterate((A,X,Y)=>X<x1|Y<y1|X>x2|Y>y2||(
      canBeFilled&=!!A
    )),
    canBeFilled&&(
      iterate((A,X,Y)=>X<x1|Y<y1|X>x2|Y>y2||(
        isFilled|=mat[Y][X]&1,
        mat[Y][X]=2
      ))
    ),
    canBeFilled&isFilled
  ),
  rectangles=0,
  iterate((a,x,y)=>a-1||(
    x1=y1=x2=y2=-1,
    start=end=0,
    iterate((A,X,Y)=>Y-y||(
      end=X,
      A||(
        start<=x&X>x&&(x1=start,x2=X-1),
        start=X+1
      )
    )),
    ~x1||(x1=start,x2=end),
    start=end=0,
    iterate((A,X,Y)=>X-x||(
      end=Y,
      A||(
        start<=y&Y>y&&(y1=start,y2=Y-1),
        start=Y+1
      )
    )),
    ~y1||(y1=start,y2=end),
    rectangles+=fill(x1,y1,x2,y2)
  )),
  ones=[],
  iterate((a,...c)=>a-1||ones.push(c)),
  ones.map((a,i)=>ones.map((b,j)=>(
    M=1,
    rectangles+=i<j&&fill(...a,...b)
  ))),
  rectangles
)
```
## Explanation
It uses similar algorithm like for solving Karnaugh maps. Firstly it tries to find at least one `1` which can be part of exactly one non-extensible rectangle. By non-extensible I mean if we extend it in any direction (up, left, right, down) it will include at least one `0` (or will go beyond the matrix bounds). When such `1` is found, find the biggest rectangle that includes it and flag all `1`s in that rectangle. Repeat until there is no more non-flagged `1`s that satisfy these conditions.
In some cases (like in the 16th test case) there are `1`s that left over after applying the first step. Then enumerate all these `1`s and apply some kind of enhanced brute-force search. This step is rarely applied, and even in these cases, we need to brute-force check only one or two combinations, so it should work very fast even for larger test cases.
[Answer]
# [Python 3](https://docs.python.org/3/)+[Numpy](https://numpy.org/), 253 bytes
```
from numpy import*
from itertools import*
R=range
P=product
def f(A):s=A.shape;r=random.rand(*s);return[N for N in R(1,sum(A)+1)for m in P(*[[(A*r)[a:b+1,c:d+1]for a,c,b,d in P(*[*map(R,s)]*2)]]*N)if len({e*all(b)for b in m for e in b.flat})==sum(A)][0]
```
[Try it online!](https://tio.run/##lZNda9swFIavp19x8C5mOSLYTcfAxRdmMFoYWUh7J0SRbbkx@AtJHgtjvz2TZMdx0w22XDg673ne82Hk/qgPXbs5nUrZNdAOTX@Equk7qQPkpEoLqbuuVrO8TyRvXwTaJb3siiHXqBAllH6KY5Wka3XgvbiTFiq6Zm3//EDhOyn0IFu6hbKTsIWqhb0fETU0xriKsFUbq@78gFI/DSSmPM5WEcnjYhUxm@ckJxkpzlTQ8N7fE4VZcIMZC7a4KqEWrf9TBLyu/cwVzSzeuK7CHrN1WXP9CyfJ2JvRkJ3ew5NQGj5zJRS4ve95/Z3LAu6HphH1BwVPD98Q0oZ6zi2VAEXvKKURYyRiZDyTVxEjV9nQ5W8usSFC83zFOO0tF11pNiZzzc2km@hMu/wY/8135hi5XeSv/VezLIlLLddls@wyUW/5cNbsadz/9j@cdp6P/8j/cb8FFS6oZf3L@wxnx8Qteyy7zBtF004bhhhC7l5zLasfBPJOSpFrewsvF4nGn1iMwPykUEOtITHfEpeSH/3Rh7HLav5iUh7dpY@PzANz1Uc@Sc5lRa2EAb6kD1@Z5zy9rFrtGyeZYAKeOnRDXUAmPJgnMrKZ0yPTpPj0Gw "Python 3 – Try It Online")
This is quite slow due to generating more than the full power set of all rectangles. I also have [an optimized version](https://tio.run/##lVRdT9swFH1efoVVHuYUUzUteynkoZqEQJoYAt4iCzmJCxGOHdkOo5r227trJ81Hy7QtD619fO65H77X1da@KLnc7TZalUjWZbVFRVkpbaeBhwrLtVVKmA4Ocr5BmpfqjT9xmQlleI41z6wJVwGC76Q9RSBt3C@zKFPSskIihuae4/koRqIwFm8KAV4wE4I0OuFYx4FMPgtuQKesBLdcbPeSPEegK5h@5nrA9AqS/9g7SqhHNkqjgiC98EbOOYekuWaWtzmEyWp1FtEmFfcV5ql3FaMrJgzvD6EUi5l5YRVP5hRdogiBhw6KPNRrfaD3qOtejoP2mO0C1pGL1oeXrAo6JrSVAopBtRTFq6uNVSjl4wpByinPWA03oqPZe4QuYxcnLJjMHbTdQ7BIoQukgZsvlISEcq6PfPrMkzmBrJ16K7NIziIC5Wuw40j/VoDhl2rOXoeVlsqOjMfy@8uesariElpyEbatZmstu@MguI819AgP7uJKq7zOrO/oDV67/jXxurm7C@1ouSpn7g9PTXjRKb1bfOtv5tbleY8jYuoS7E@j0KGlQ6FT00IyV0GDD8clweupDhO2Sk8jkq3y04g6Q0YykhJ/XXd4mkxLVuF7YkI6XYQ0JLchFEFwiX/yKQwLTr231NFLHw53y3S2Ecz@CuO4CSrcnaBHbiz6ygxMkJ/qaybemM7RdV2WXHw26PHmexBYYD1ljgXzEnxKEuhfSiJKmjUZ7Sg5OJ3780W/B4ZrjxHHY8e86ABze9JpLlscdnu2P2/2f7Lb8yg5H5wf2h/EMmT0Wt7LcuilZR3z5x3mVk3@5/9h6eL58o/8D/MbsOYD1lC/r@e8s2h5Qx9DL11GUZvTkgY0CHy3M6uLdwINr92AuRbsG2nVjqCphYVZ32CmNdvixqZ95y17hqNJcrd@eKAT/7B4fhzvJd2zCISr9c03OvE2lS6kxWBJWjJBE/OiapHDKzdBXTQAQ4wT0kYZ7n4D) to test the slower test cases, which makes three optimizations:
* Uses combinations of N rectangles instead of cartesian product to avoid duplicate rectangles
* Uses `next(...)` instead of `[...][0]` to terminate as soon as a single covering is found with minimal number of rectangles
* Prunes out rectangles that are subsets of larger rectangles containing only 1s (96% reduction in rectangles for the largest test case)
## Semi-ungolfed
```
from numpy import*
from itertools import*
R=range
# P is the itertools product
P=product
def f(A):
    # take input as a numpy array A
    s = A.shape;
    # r is a weight matrix that gives a distinct value to each entry in A
    # (probability 1)
    # Longer alternative (can be inlined): r=~reshape(range(h*w),(h,w))
    r = random.rand(*s);
    return [
        # We're finding number of rectangles N
        N
        # In the range [1..sum(A)] where sum(A) is the number of 1s (upper bound for # rectangles)
        for N in R(1, sum(A)+1)
        # m is a combination of N possible rectangles
        for m in P(
            # (this is Cartesian product instead of combinations, but duplicates
            # provide no benefit)
            # P(*[...]*N) === product(..., repeat=N)
            *[
                [
                    # A*r is modified array such that each 1 is given
                    # a distinct value and all 0s remain 0
                    # all rectangles in A*r
                    (A*r)[a:b+1, c:d+1]
                    for a,c,b,d in P(*[*map(R,s)]*2)
                ]
            ]*N
        )
        # Test that each rectangle contains no 0s and the combination of rectangles covers all 1s:
        if len({e*all(b) for b in m for e in b.flat})==sum(A)
    # Get the first N with a valid covering
    # must be min N because N is generated in order
    ][0]
```
]