Python 2 & PuLP - 2.644.688 ô vuông (tối thiểu hóa tối ưu); 10,753,553 hình vuông (tối đa hóa tối đa)
Tối thiểu được đánh gôn tới 1152 byte
from pulp import*
x=0
f=open("c","r")
g=open("s","w")
for k,m in enumerate(f):
if k%2:
b=map(int,m.split())
p=LpProblem("Nn",LpMinimize)
q=map(str,range(18))
ir=q[1:18]
e=LpVariable.dicts("c",(q,q),0,1,LpInteger)
rs=LpVariable.dicts("rs",(ir,ir),0,1,LpInteger)
cs=LpVariable.dicts("cs",(ir,ir),0,1,LpInteger)
p+=sum(e[r][c] for r in q for c in q),""
for i in q:p+=e["0"][i]==0,"";p+=e[i]["0"]==0,"";p+=e["17"][i]==0,"";p+=e[i]["17"]==0,""
for o in range(289):i=o/17+1;j=o%17+1;si=str(i);sj=str(j);l=e[si][str(j-1)];ls=rs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,"";l=e[str(i-1)][sj];ls=cs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,""
for r,z in enumerate(a):p+=lpSum([rs[str(r+1)][c] for c in ir])==2*z,""
for c,z in enumerate(b):p+=lpSum([cs[r][str(c+1)] for r in ir])==2*z,""
p.solve()
for r in ir:
for c in ir:g.write(str(int(e[r][c].value()))+" ")
g.write('\n')
g.write('%d:%d\n\n'%(-~k/2,value(p.objective)))
x+=value(p.objective)
else:a=map(int,m.split())
print x
(NB: các dòng thụt nhiều bắt đầu bằng các tab, không phải dấu cách.)
Kết quả ví dụ: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=shaming
Hóa ra các vấn đề như có thể chuyển đổi dễ dàng sang Chương trình tuyến tính Integer và tôi cần một vấn đề cơ bản để tìm hiểu cách sử dụng giao diện python của PuLP cho một loạt các trình giải LP cho một dự án của riêng tôi. Nó cũng chỉ ra rằng PuLP cực kỳ dễ sử dụng và trình xây dựng LP không được phép hoạt động hoàn hảo ngay lần đầu tiên tôi dùng thử.
Hai điều tốt đẹp về việc sử dụng một bộ giải IP liên kết và chi nhánh để thực hiện công việc khó khăn này để giải quyết vấn đề này cho tôi (ngoài việc không phải thực hiện một chi nhánh và bộ giải ràng buộc) là
- Người giải quyết mục đích xây dựng là thực sự nhanh chóng. Chương trình này giải quyết tất cả 50000 vấn đề trong khoảng 17 giờ trên PC tại nhà tương đối thấp của tôi. Mỗi trường hợp mất từ 1-1,5 giây để giải quyết.
- Họ sản xuất các giải pháp tối ưu được đảm bảo (hoặc nói với bạn rằng họ đã không làm như vậy). Vì vậy, tôi có thể tự tin rằng sẽ không có ai đánh bại điểm số của tôi trong các ô vuông (mặc dù ai đó có thể buộc nó và đánh bại tôi trong phần chơi gôn).
Cách sử dụng chương trình này
Trước tiên, bạn sẽ cần cài đặt PuLP. pip install pulp
nên thực hiện các mẹo nếu bạn đã cài đặt pip.
Sau đó, bạn sẽ cần đặt đoạn mã sau vào một tệp có tên "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=shaming
Sau đó, chạy chương trình này trong bất kỳ bản dựng Python 2 muộn nào từ cùng một thư mục. Trong vòng chưa đầy một ngày, bạn sẽ có một tệp có tên "s" chứa 50.000 lưới không chữ được giải (ở định dạng có thể đọc được), mỗi tệp có tổng số ô vuông được liệt kê bên dưới.
Thay vào đó, nếu bạn muốn tối đa hóa số lượng hình vuông đầy, thay đổi LpMinimize
dòng 8 thành LpMaximize
thay thế. Bạn sẽ nhận được đầu ra rất giống như thế này: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=shaming
định dạng đầu vào
Chương trình này sử dụng định dạng đầu vào được sửa đổi, vì Joe Z. nói rằng chúng tôi sẽ được phép mã hóa lại định dạng đầu vào nếu chúng tôi muốn trong một nhận xét về OP. Nhấp vào liên kết ở trên để xem nó trông như thế nào. Nó bao gồm 10000 dòng, mỗi dòng chứa 16 số. Các dòng được đánh số chẵn là độ lớn cho các hàng của một thể hiện nhất định, trong khi các dòng được đánh số lẻ là độ lớn cho các cột của cùng thể hiện với dòng trên chúng. Tập tin này được tạo bởi chương trình sau:
from bitqueue import *
with open("nonograms_b64.txt","r") as f:
with open("nonogram_clues.txt","w") as g:
for line in f:
q = BitQueue(line.decode('base64'))
nonogram = []
for i in range(256):
if not i%16: row = []
row.append(q.nextBit())
if not -~i%16: nonogram.append(row)
s=""
for row in nonogram:
blocks=0 #magnitude counter
for i in range(16):
if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
s+=str(blocks)+" "
print >>g, s
nonogram = map(list, zip(*nonogram)) #transpose the array to make columns rows
s=""
for row in nonogram:
blocks=0
for i in range(16):
if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
s+=str(blocks)+" "
print >>g, s
(Chương trình mã hóa lại này cũng cho tôi thêm cơ hội để kiểm tra lớp BitQueue tùy chỉnh mà tôi đã tạo cho cùng một dự án được đề cập ở trên. Nó chỉ đơn giản là một hàng đợi để dữ liệu có thể được đẩy thành chuỗi bit HOẶC byte và từ đó dữ liệu có thể được bật lên một bit hoặc một byte tại một thời điểm. Trong trường hợp này, nó hoạt động hoàn hảo.)
Tôi đã mã hóa lại đầu vào với lý do cụ thể là để xây dựng ILP, thông tin bổ sung về các lưới được sử dụng để tạo ra cường độ là hoàn toàn vô dụng. Độ lớn là những hạn chế duy nhất, và vì vậy độ lớn là tất cả những gì tôi cần truy cập.
Người xây dựng ILP bị phản đối
from pulp import *
total = 0
with open("nonogram_clues.txt","r") as f:
with open("solutions.txt","w") as g:
for k,line in enumerate(f):
if k%2:
colclues=map(int,line.split())
prob = LpProblem("Nonogram",LpMinimize)
seq = map(str,range(18))
rows = seq
cols = seq
irows = seq[1:18]
icols = seq[1:18]
cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)
prob += sum(cells[r][c] for r in rows for c in cols),""
for i in rows:
prob += cells["0"][i] == 0,""
prob += cells[i]["0"] == 0,""
prob += cells["17"][i] == 0,""
prob += cells[i]["17"] == 0,""
for i in range(1,18):
for j in range(1,18):
si = str(i); sj = str(j)
l = cells[si][str(j-1)]; ls = rowseps[si][sj]
prob += cells[si][sj] <= l + ls,""
prob += cells[si][sj] >= l - ls,""
prob += cells[si][sj] >= ls - l,""
prob += cells[si][sj] <= 2 - ls - l,""
l = cells[str(i-1)][sj]; ls = colseps[si][sj]
prob += cells[si][sj] <= l + ls,""
prob += cells[si][sj] >= l - ls,""
prob += cells[si][sj] >= ls - l,""
prob += cells[si][sj] <= 2 - ls - l,""
for r,clue in enumerate(rowclues):
prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
for c,clue in enumerate(colclues):
prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""
prob.solve()
print "Status for problem %d: "%(-~k/2),LpStatus[prob.status]
for r in rows[1:18]:
for c in cols[1:18]:
g.write(str(int(cells[r][c].value()))+" ")
g.write('\n')
g.write('Filled squares for %d: %d\n\n'%(-~k/2,value(prob.objective)))
total += value(prob.objective)
else:
rowclues=map(int,line.split())
print "Total number of filled squares: %d"%total
Đây là chương trình thực sự tạo ra "ví dụ đầu ra" được liên kết ở trên. Do đó, các chuỗi dài thêm ở cuối mỗi lưới, mà tôi đã cắt bớt khi chơi golf. (Phiên bản được đánh gôn sẽ tạo ra đầu ra giống hệt nhau, trừ các từ "Filled squares for "
)
Làm thế nào nó hoạt động
cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)
Tôi sử dụng lưới 18x18, với phần 16x16 ở giữa là giải pháp câu đố thực tế. cells
là lưới này. Dòng đầu tiên tạo ra 324 biến nhị phân: "cell_0_0", "cell_0_1", v.v. Tôi cũng tạo các lưới của "khoảng trắng" giữa và xung quanh các ô trong phần giải pháp của lưới. rowseps
chỉ ra 289 biến tượng trưng cho các không gian phân tách các ô theo chiều ngang, trong khi colseps
tương tự chỉ đến các biến đánh dấu các không gian phân tách các ô theo chiều dọc. Đây là một sơ đồ unicode:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
- - - - - - - - - - - - - - - -
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Các 0
s và □
s là các giá trị nhị phân theo dõi bởi các cell
biến, |
s là những giá trị nhị phân theo dõi bởi các rowsep
biến, và -
s là các giá trị nhị phân theo dõi bởi các colsep
biến.
prob += sum(cells[r][c] for r in rows for c in cols),""
Đây là chức năng khách quan. Chỉ là tổng của tất cả các cell
biến. Vì đây là các biến nhị phân, đây chỉ chính xác là số lượng hình vuông đầy trong giải pháp.
for i in rows:
prob += cells["0"][i] == 0,""
prob += cells[i]["0"] == 0,""
prob += cells["17"][i] == 0,""
prob += cells[i]["17"] == 0,""
Điều này chỉ đặt các ô xung quanh mép ngoài của lưới thành 0 (đó là lý do tại sao tôi biểu diễn chúng dưới dạng số 0 ở trên). Đây là cách tốt nhất để theo dõi có bao nhiêu "khối" ô được điền, vì nó đảm bảo rằng mọi thay đổi từ không được điền sang được điền (di chuyển qua một cột hoặc hàng) được khớp với một thay đổi tương ứng từ được điền vào không được lấp đầy (và ngược lại ), ngay cả khi ô đầu tiên hoặc ô cuối cùng trong hàng được điền. Đây là lý do duy nhất để sử dụng lưới 18x18 ở vị trí đầu tiên. Đây không phải là cách duy nhất để đếm các khối, nhưng tôi nghĩ đó là cách đơn giản nhất.
for i in range(1,18):
for j in range(1,18):
si = str(i); sj = str(j)
l = cells[si][str(j-1)]; ls = rowseps[si][sj]
prob += cells[si][sj] <= l + ls,""
prob += cells[si][sj] >= l - ls,""
prob += cells[si][sj] >= ls - l,""
prob += cells[si][sj] <= 2 - ls - l,""
l = cells[str(i-1)][sj]; ls = colseps[si][sj]
prob += cells[si][sj] <= l + ls,""
prob += cells[si][sj] >= l - ls,""
prob += cells[si][sj] >= ls - l,""
prob += cells[si][sj] <= 2 - ls - l,""
Đây là thịt thực sự của logic của ILP. Về cơ bản, nó yêu cầu mỗi ô (trừ các ô trong hàng và cột đầu tiên) phải là xor logic của ô và dấu phân cách trực tiếp bên trái trong hàng của nó và ngay phía trên nó trong cột của nó. Tôi đã nhận được các ràng buộc mô phỏng một xor trong chương trình số nguyên {0,1} từ câu trả lời tuyệt vời này: /cs//a/12118/44289
Để giải thích thêm một chút: ràng buộc xor này làm cho nó để các dấu phân cách có thể là 1 khi và chỉ khi chúng nằm giữa các ô là 0 và 1 (đánh dấu một sự thay đổi từ không được điền thành hoặc ngược lại). Do đó, sẽ có chính xác gấp đôi số phân cách có giá trị 1 trong một hàng hoặc cột so với số khối trong hàng hoặc cột đó. Nói cách khác, tổng của các dấu phân cách trên một hàng hoặc cột đã cho chính xác gấp đôi độ lớn của hàng / cột đó. Do đó các ràng buộc sau:
for r,clue in enumerate(rowclues):
prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
for c,clue in enumerate(colclues):
prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""
Và đó là khá nhiều đó. Phần còn lại chỉ yêu cầu người giải mặc định giải ILP, sau đó định dạng giải pháp kết quả khi nó ghi vào tệp.