Python 2 ve PuLP - 2.644.688 kare (en iyi şekilde küçültülmüş); 10.753.553 kare (en iyi duruma getirilmiş)
En az 1152 bayta golf yapıldı
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
(Not: yoğun girintili çizgiler boşluklarla değil sekmelerle başlar.)
Örnek çıktı: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
Görünüşe göre gibi problemler, Tamsayı Doğrusal Programlara kolayca dönüştürülebilir ve benim de bir proje için çeşitli LP çözücüleri için bir python arayüzü olan PuLP'nin nasıl kullanılacağını öğrenmek için temel bir probleme ihtiyacım vardı. Ayrıca PuLP'nin kullanımının son derece kolay olduğu ve ungolfed LP üreticisinin ilk denediğimde mükemmel çalıştığı ortaya çıktı.
Bunu benim için çözmenin zor işini yapmak için bir şube ve bağlı IP çözücüsü kullanmakla ilgili iki güzel şey (bir şube ve bağlı çözücü uygulamak zorunda kalmak dışında)
- Amaca uygun çözücüler gerçekten hızlı. Bu program nispeten düşük kaliteli ev bilgisayarımda yaklaşık 17 saat içinde 50000 tüm sorunları çözer. Her örnek, çözülmesi 1-1.5 saniye sürdü.
- Garantili optimum çözümler üretir (ya da bunu yapamadıklarını söylerler). Böylece, kimsenin karelerimdeki puanı geçemeyeceğinden emin olabilirim (biri onu bağlayabilir ve golf bölümünde beni yenebilir).
Bu program nasıl kullanılır?
İlk olarak, PuLP'yi kurmanız gerekir. pip install pulp
pip yüklü ise hile yapmalı.
Ardından, aşağıdakileri "c" adlı bir dosyaya koymanız gerekir: https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing
Daha sonra, bu programı aynı dizinden herhangi bir geç Python 2 derlemesinde çalıştırın. Bir günden az bir sürede, her biri altında listelenen toplam doldurulmuş kare sayısı olan, 50.000 çözülmüş, program dışı ızgara (okunabilir biçimde) içeren "s" adlı bir dosyaya sahip olacaksınız.
Bunun yerine dolu karelerin sayısını en üst düzeye çıkarmak istiyorsanız, LpMinimize
on satır 8'i LpMaximize
bunun yerine olarak değiştirin. Çok benzer çıktılar alacaksınız: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing
Giriş biçimi
Bu program değiştirilmiş bir giriş formatı kullanır, çünkü Joe Z. OP'ye yaptığı bir yorumda isterseniz giriş formatını yeniden kodlamamıza izin verileceğini söyledi. Neye benzediğini görmek için yukarıdaki bağlantıyı tıklayın. Her biri 16 sayı içeren 10000 satırdan oluşur. Çift numaralı çizgiler, belirli bir örneğin satırları için büyüklüklerken, tek numaralı çizgiler, üstlerindeki çizgi ile aynı örneğin sütunları için büyüklüklerdir. Bu dosya aşağıdaki program tarafından oluşturulmuştur:
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
(Bu yeniden kodlama programı ayrıca yukarıda belirtilen aynı proje için oluşturduğum özel BitQueue sınıfımı test etmek için ekstra bir fırsat verdi. Bu, verilerin bit OR bayt dizileri olarak gönderilebildiği ve hangi verilerin bir seferde bir bit veya bir bayt atılabilir. Bu durumda, mükemmel çalıştı.)
Bir ILP oluşturmak için girdiyi yeniden kodladım, büyüklükleri oluşturmak için kullanılan ızgaralar hakkında ekstra bilgi mükemmel bir şekilde işe yaramaz. Büyüklükler tek kısıtlamadır ve bu yüzden büyüklükler erişmem gereken tek şeydir.
Ungolfed ILP oluşturucu
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
Bu aslında yukarıda bağlantılı "örnek çıktı" üreten programdır. Bu nedenle, golf yaparken kesildiğim her ızgaranın sonundaki ekstra uzun teller. (Golfçü versiyon, kelimeler eksi aynı çıktı üretmelidir "Filled squares for "
)
Nasıl çalışır
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)
18x18 ızgara kullanıyorum, merkez 16x16 kısmı gerçek bulmaca çözümü. cells
bu ızgara. İlk satır 324 ikili değişken oluşturur: "cell_0_0", "cell_0_1" vb. Ayrıca, ızgaranın çözüm kısmındaki hücreler arasında ve etrafındaki "boşlukların" ızgaralarını oluşturuyorum. rowseps
hücreleri yatay olarak ayıran boşlukları simgeleyen 289 değişkeni gösterirken, colseps
hücreleri dikey olarak ayıran boşlukları işaretleyen değişkenleri gösterir. İşte bir unicode diyagramı:
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
0
S ve □
s tarafından izlenen ikili değerler cell
değişkenler, |
s ikili değerler tarafından izlenen olan rowsep
değişkenleri ve -
s ikili değerler tarafından izlenen olan colsep
değişkenler.
prob += sum(cells[r][c] for r in rows for c in cols),""
Amaç işlevi budur. Sadece tüm cell
değişkenlerin toplamı . Bunlar ikili değişkenler olduğundan, bu tam olarak çözümdeki doldurulmuş karelerin sayısıdır.
for i in rows:
prob += cells["0"][i] == 0,""
prob += cells[i]["0"] == 0,""
prob += cells["17"][i] == 0,""
prob += cells[i]["17"] == 0,""
Bu sadece ızgaranın dış kenarı etrafındaki hücreleri sıfıra ayarlar (bu yüzden onları yukarıda sıfır olarak temsil ettim). Bu, kaç hücre bloğunun dolduğunu izlemenin en uygun yoludur, çünkü doldurulmadan doldurulan (bir sütun veya satır boyunca hareket eden) her değişikliğin doldurulmuştan doldurulmamışa (veya tersi) karşılık gelen bir değişiklikle eşleşmesini sağlar. ), satırdaki ilk veya son hücre doldurulmuş olsa bile. İlk etapta 18x18 ızgara kullanmanın tek nedeni budur. Blokları saymanın tek yolu bu değil, ama bence en basit olanı bu.
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,""
Bu, ILP mantığının gerçek etidir. Temel olarak, her bir hücrenin (ilk satır ve sütundaki hücreler dışında), hücrenin ve ayırıcısının doğrudan satırında soluna ve doğrudan sütunun üstünde mantıksal xor olmasını gerektirir. Bu harika yanıttan {0,1} tam sayı programı içinde bir xor'u simüle eden kısıtlamaları aldım: /cs//a/12118/44289
Biraz daha açıklamak gerekirse: bu xor kısıtlaması, ayırıcıların 1 ve sadece 0 ve 1 olan hücreler arasında yer almaları durumunda 1 olmasını sağlar (doldurulmadan doldurulmaya veya tam tersi). Böylece, bir satır veya sütunda, o satır veya sütundaki blok sayısının tam iki katı kadar 1 değerli ayırıcı olacaktır. Başka bir deyişle, belirli bir satır veya sütundaki ayırıcıların toplamı, o satırın / sütunun büyüklüğünün tam olarak iki katıdır. Bu nedenle aşağıdaki kısıtlamalar:
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,""
Ve işte bu kadar. Geri kalanlar varsayılan çözücüden ILP'yi çözmesini ister, daha sonra elde edilen çözümü dosyaya yazarken biçimlendirir.