Python 2 & PuLP - 2.644.688 kotak (diminimalkan secara optimal); 10.753.553 kotak (dimaksimalkan secara optimal)
Minimal bermain golf hingga 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: garis yang indentasi dimulai dengan tab, bukan spasi.)
Contoh keluaran: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
Ternyata masalah seperti mudah dikonversi ke Integer Linear Programme, dan saya membutuhkan masalah dasar untuk belajar bagaimana menggunakan PuLP — antarmuka python untuk berbagai pemecah LP — untuk proyek saya sendiri. Ternyata PuLP juga sangat mudah digunakan, dan pembangun LP yang tidak diserang bekerja dengan sempurna saat pertama kali saya mencobanya.
Dua hal yang baik tentang menggunakan pemecah IP cabang-dan-terikat untuk melakukan kerja keras memecahkan ini untuk saya (selain tidak harus menerapkan pemecah cabang dan terikat) adalah bahwa
- Pemecah yang dibuat khusus sangat cepat. Program ini menyelesaikan semua masalah 50000 dalam waktu sekitar 17 jam pada PC rumahan saya yang relatif rendah. Setiap instance membutuhkan waktu 1-1,5 detik untuk menyelesaikannya.
- Mereka menghasilkan solusi optimal yang dijamin (atau memberi tahu Anda bahwa mereka gagal melakukannya). Dengan demikian, saya dapat yakin bahwa tidak ada yang akan mengalahkan skor saya di kotak (meskipun seseorang mungkin mengikatnya dan mengalahkan saya di bagian golf).
Cara menggunakan program ini
Pertama, Anda harus menginstal PuLP. pip install pulp
harus melakukan trik jika Anda telah menginstal pip.
Kemudian, Anda harus meletakkan yang berikut ini dalam file bernama "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing
Kemudian, jalankan program ini di Python 2 build apa pun dari direktori yang sama. Dalam waktu kurang dari sehari, Anda akan memiliki file bernama "s" yang berisi 50.000 grid nonogram yang diselesaikan (dalam format yang dapat dibaca), masing-masing dengan jumlah total kotak yang diisi tercantum di bawahnya.
Jika Anda ingin memaksimalkan jumlah kuadrat yang diisi, ganti LpMinimize
baris on ke 8 LpMaximize
sebagai gantinya. Anda akan mendapatkan hasil sangat banyak seperti ini: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing
Masukkan format
Program ini menggunakan format input yang dimodifikasi, karena Joe Z. mengatakan bahwa kami akan diizinkan untuk menyandikan ulang format input jika kami suka dalam komentar di OP. Klik tautan di atas untuk melihat seperti apa bentuknya. Ini terdiri dari 10.000 baris, masing-masing berisi 16 angka. Garis bernomor genap adalah magnitudo untuk baris dari instance yang diberikan, sedangkan garis bernomor ganjil adalah magnitudo untuk kolom dengan instance yang sama dengan garis di atasnya. File ini dihasilkan oleh program berikut:
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
(Program pengodean ulang ini juga memberi saya kesempatan ekstra untuk menguji kelas BitQueue kustom yang saya buat untuk proyek yang sama yang disebutkan di atas. Ini hanyalah antrian yang datanya dapat didorong sebagai urutan bit ATAU byte, dan dari mana data dapat muncul sedikit atau byte pada suatu waktu. Dalam hal ini, itu bekerja dengan sempurna.)
Saya mengkodekan ulang input untuk alasan spesifik bahwa untuk membangun ILP, informasi tambahan tentang grid yang digunakan untuk menghasilkan besaran sama sekali tidak berguna. Magnitudo adalah satu-satunya kendala, dan magnitudo itulah yang saya butuhkan untuk mengakses.
Pembangun ILP tak berkerumun
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
Ini adalah program yang benar-benar menghasilkan "contoh keluaran" yang ditautkan di atas. Oleh karena itu string ekstra panjang di ujung setiap kotak, yang saya terpotong saat bermain golf. (Versi golf harus menghasilkan output yang identik, minus kata-kata "Filled squares for "
)
Bagaimana itu bekerja
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)
Saya menggunakan kisi 18x18, dengan bagian tengah 16x16 menjadi solusi puzzle yang sebenarnya. cells
apakah kotak ini. Baris pertama menciptakan 324 variabel biner: "cell_0_0", "cell_0_1", dan seterusnya. Saya juga membuat kisi-kisi "ruang" antara dan di sekitar sel di bagian solusi kisi. rowseps
menunjuk ke 289 variabel yang melambangkan ruang yang memisahkan sel secara horizontal, sementara colseps
juga menunjuk ke variabel yang menandai ruang yang memisahkan sel secara vertikal. Berikut diagram 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
The 0
s dan □
s adalah nilai-nilai biner dilacak oleh cell
variabel, yang |
s adalah nilai-nilai biner dilacak oleh rowsep
variabel, dan -
s adalah nilai-nilai biner dilacak oleh colsep
variabel.
prob += sum(cells[r][c] for r in rows for c in cols),""
Ini adalah fungsi tujuan. Hanya jumlah dari semua cell
variabel. Karena ini adalah variabel biner, ini persis jumlah kuadrat yang diisi dalam solusi.
for i in rows:
prob += cells["0"][i] == 0,""
prob += cells[i]["0"] == 0,""
prob += cells["17"][i] == 0,""
prob += cells[i]["17"] == 0,""
Ini hanya mengatur sel di sekitar tepi luar grid ke nol (itulah sebabnya saya mewakili mereka sebagai nol di atas). Ini adalah cara paling bijaksana untuk melacak berapa banyak "blok" sel yang terisi, karena memastikan bahwa setiap perubahan dari tidak terisi menjadi terisi (bergerak melintasi kolom atau baris) dicocokkan dengan perubahan yang sesuai dari terisi ke tidak terisi (dan sebaliknya ), bahkan jika sel pertama atau terakhir di baris terisi. Ini adalah alasan utama untuk menggunakan kisi 18x18 di tempat pertama. Ini bukan satu-satunya cara untuk menghitung blok, tetapi saya pikir ini adalah yang paling sederhana.
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,""
Ini adalah daging asli dari logika ILP. Pada dasarnya ia mensyaratkan bahwa setiap sel (selain yang ada di baris dan kolom pertama) menjadi karakter logis dari sel dan pemisah langsung ke kiri di barisnya dan langsung di atasnya dalam kolomnya. Saya mendapat kendala yang mensimulasikan xor dalam program integer {0,1} dari jawaban yang luar biasa ini: /cs//a/12118/44289
Untuk menjelaskan lebih banyak: batasan xor ini membuatnya sehingga separator bisa 1 jika dan hanya jika mereka terletak di antara sel-sel yang 0 dan 1 (menandai perubahan dari tidak terisi menjadi terisi atau sebaliknya). Dengan demikian, akan ada tepat dua kali lebih banyak pemisah bernilai 1 dalam satu baris atau kolom daripada jumlah blok di baris atau kolom itu. Dengan kata lain, jumlah pemisah pada baris atau kolom tertentu persis dua kali lipat besarnya baris / kolom itu. Karenanya kendala berikut:
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,""
Dan itu sudah cukup. Sisanya hanya meminta solver default untuk menyelesaikan ILP, lalu memformat solusi yang dihasilkan saat ia menulisnya ke file.