3

I was trying to write a general program to check The 17x17 problem SOLVED!, 4-coloring of a17x17 grid with no monochromatic rectangles. Solution link: 17.txt.

This is what I wrote:

from itertools import product

def is_solution(myfile,m,n):
    """ m-lines, n-columns """
    grid = [c.strip() for c in line.split(',')] for line in open(myfile).readlines()]
    for x0,y0 in product(xrange(m),xrange(n)):
        start = grid[x0][y0]
        for x in xrange(x0+1,m):
            if grid[x][y0] == start:
                for y in xrange(y0+1,n):
                    if grid[x0][y] == start == grid[x][y]:
                            return False
    return True


print is_solution('17.txt',17,17)

Is there a more readable, concise or efficient way (in that order of priority) to write this? Maybe a different approach with different data structures... Since i am learning Python at the moment, any advice is very welcome.

Ken White
  • 123,280
  • 14
  • 225
  • 444
ChessMaster
  • 529
  • 1
  • 4
  • 12
  • How did you find the 17x17 grid? was it using 17 by 17 MOLS (Mutually orthogonal latin squares)? Since there are 16=4x4 you could use them to map out the rectangles? I wonder if I could make the 17x17 grid by hand – Rusty Rob Feb 11 '12 at 10:31

4 Answers4

4
  1. You should cleanly separate the logic for in-/output from the verification logic (this basically applies to any code). This also includes that you only put definitions at the module's top level. The actual program usually goes into a conditional like in the code below which is only executed if the file is called directly from the command line (and not if it is only imported by another file).
  2. The dimensions of the grid can be derived from the input. No need for separate parameters here.
  3. You should work with integers instead of strings (this is optional, but more clean, IMO)

My attempt (taking the file from STDIN, can be called like python script.py < 17.txt):

import itertools

def has_monochromatic_rectangles(grid):
  # use range instead of xrange here (xrange is not in Python 3)
  points = list(itertools.product(range(len(grid)), range(len(grid[0]))))
  # check if for any rectangle, all 4 colors are equal
  # (this is more brute-force than necessary, but you placed simplicity
  # above efficiency. Also, for 17x17, it doesn't matter at all ;)
  return any(grid[x1][y1] == grid[x1][y2] == grid[x2][y1] == grid[x2][y2]
             for (x1,y1), (x2,y2) in itertools.product(points, points)
             if x1 != x2 and y1 != y2)

def has_max_colors(grid, most):
  # collect all grid values and uniquify them by creating a set
  return len(set(sum(grid, []))) <= most

if __name__ == '__main__':
  # read from STDIN (could easily be adapted to read from file, URL, ...)
  import sys
  grid = [map(int, line.split(',')) for line in sys.stdin]

  assert has_max_colors(grid, 4)
  assert not has_monochromatic_rectangles(grid)
Niklas B.
  • 92,950
  • 18
  • 194
  • 224
1
import urllib
grid=urllib.urlopen("http://www.cs.umd.edu/~gasarch/BLOGPAPERS/17.txt")
grid=[map(int,row.split(",")) for row in grid]
print grid

def check_grid(grid):
    for i in range(17):
        for j in range(17):
            for i2 in range(i):
                for j2 in range(j):
                    colours=[grid[a][b] for a in (i,i2) for b in (j,j2)]
                    assert(len(set(colours))>1)

check_grid(grid)
grid[1][1]=2
check_grid(grid)
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
  • Why not keep the `itertools.product`? It could be used to clean up those loops at least a bit. Also, calling `readlines` is unnecessary. – Niklas B. Feb 11 '12 at 03:44
  • I probably should have kept itertools.product. I've remeoved the readlines() part cheers. – Rusty Rob Feb 11 '12 at 03:47
1

Obligatory one-liner*!

Assuming you've loaded the 17x17 data into a numpy array named A (see @robertking's answer to use urllib), you can do it with one line with numpy!

 print (array([len(set(A[i:i+k1,j:j+k2][zip(*[(0,0), (0,-1),(-1,0),(-1,-1)])])) for i in xrange(16) for j in xrange(16) for k1 in xrange(2,17) for k2 in xrange(2,17)])!=1).all()

* Don't actually do this in one-line. Here it is expanded out a bit for clarity:

corners = zip(*[(0,0), (0,-1),(-1,0),(-1,-1)])

for k1 in xrange(2,17):
    for k2 in xrange(2,17):
        for i in xrange(16):
            for j in xrange(16):

                # Pull out each sub-rectange
                sub = A[i:i+k1, j:j+k2]

                # Only use the corners
                sub = sub[corners]

                # Count the number of unique elements
                uniq = len(set(sub))

                # Check if all corners are the same
                if uniq == 1: 
                    print False
                    exit()
print True
Hooked
  • 84,485
  • 43
  • 192
  • 261
  • ah I don't have numpy installed at the moment. what does flatten() do again? Does this test all rectangles? or just 2x2 rectangles? – Rusty Rob Feb 11 '12 at 04:02
  • @robertking, it did only check 2x2, I realized the problem was to check _all_ rectangles. Fixed it now. Removed flatten when I went to corner indexing. Flatten just takes a multi-dimensional numpy array and makes it 1D. – Hooked Feb 11 '12 at 04:07
  • @NiklasB. Only the all-mighty Carriage Return can be the arbiter on the singularity of my line. – Hooked Feb 11 '12 at 04:12
  • @NiklasB. Ninja edit - code is right, comment was wrong. `uniq` is the number of unique elements in the set of the four corners. If `uniq==1` then we have the case where the coloring has failed. – Hooked Feb 11 '12 at 04:19
  • @Hooked: Yeah, my mistake. Removed the comment. Still I think this shouldn't be advertised as a "one-liner". – Niklas B. Feb 11 '12 at 04:20
  • @NiklasB. perhaps you're right. I've added a warning for the foolhardy and naive not to do this. I figured _someone_ would get the joke that a question about code clarity should not be given a verbose one-liner. Either way, the downvotes seem to agree with you! – Hooked Feb 11 '12 at 04:28
  • i think you mean to have corners=sub[corners] – Rusty Rob Feb 11 '12 at 05:27
  • @robertking No I mean it as written. `corners` is a numpy index to the four corners of the numpy array, as such `corners` never changes. I simply discard the rest of the values by indexing over what I want. – Hooked Feb 11 '12 at 06:03
0

Here is another way to think about it.

import urllib
grid=urllib.urlopen("http://www.cs.umd.edu/~gasarch/BLOGPAPERS/17.txt")
grid=[map(int,row.split(",")) for row in grid]

def check(grid):
    colour_positions=lambda c,row:set(i for i,colour in enumerate(row) if colour==c) #given a row and a colour, where in the row does that colour occur
    to_check=[[colour_positions(c,row) for row in grid] for c in range(1,5)] #for each row and each colour, get the horizontal positions.
    from itertools import combinations
    for i in to_check:
        for a,b in combinations(i,2):
            if len(a&b)>1: #for each colour, for each combination of rows, do we ever get more than 1 horizontal position in common (e.g. a rectangle)
                return False
    return True

print check(grid)
grid[1][1]=2
print check(grid)
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116