Python 2およびPuLP — 2,644,688平方(最適化された最小化); 10,753,553平方(最適化された最大化)
1152バイトまで最小限にゴルフ
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
(注意:大きくインデントされた行は、スペースではなくタブで始まります。)
出力例:https : //drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
のような問題は整数線形プログラムに容易に変換可能であることがわかり、私は独自のプロジェクトでさまざまなLPソルバー用のPythonインターフェイスであるPuLPを使用する方法を学ぶための基本的な問題が必要でした。また、PuLPは非常に使いやすく、最初に試してみたときに、未開発のLPビルダーが完全に機能したことがわかりました。
ブランチアンドバウンドIPソルバーを使用してこれを解決するためのハードワークを行うことに関する2つの良い点(ブランチアンドバウンドソルバーを実装する必要がないこと)
- 専用のソルバーは非常に高速です。このプログラムは、比較的低価格の自宅のPCで約17時間で50000件の問題をすべて解決します。各インスタンスの解決には1〜1.5秒かかりました。
- 保証された最適なソリューションを生成します(または、そうしなかったことを伝えます)。したがって、私は誰も私のスコアを四角で打つことはないと確信できます(誰かがそれを結びつけてゴルフの部分で私を打つかもしれませんが)。
このプログラムの使用方法
まず、PuLPをインストールする必要があります。pip install pulp
pipがインストールされている場合は、トリックを行う必要があります。
次に、「c」というファイルに次のファイルを配置する必要があります。https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp = sharing
次に、同じディレクトリからPython 2以降のビルドでこのプログラムを実行します。1日も経たないうちに、50,000個の解かれた非グラムグリッド(読み取り可能な形式)を含む「s」というファイルがあり、それぞれの下にリストされている塗りつぶされた正方形の総数があります。
代わりに塗りつぶされた正方形の数を最大化する場合は、代わりにLpMinimize
8行目を変更しLpMaximize
ます。次のような出力が得られます:https : //drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing
入力形式
Joe Z.がOPのコメントで必要に応じて入力形式を再エンコードできると述べたため、このプログラムは修正された入力形式を使用します。上のリンクをクリックして、表示を確認します。10000行で構成され、各行には16個の数字が含まれています。偶数行は特定のインスタンスの行の大きさであり、奇数行はその上の行と同じインスタンスの列の大きさです。このファイルは、次のプログラムによって生成されました。
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
(この再エンコードプログラムは、上記の同じプロジェクト用に作成したカスタムBitQueueクラスをテストする機会も与えてくれました。これは、データをビットまたはバイトのシーケンスとしてプッシュしたり、一度にビットまたはバイトのいずれかでポップされます。この例では、完全に機能しました。)
ILPを構築する特定の理由で入力を再エンコードしましたが、振幅の生成に使用されたグリッドに関する追加情報はまったく役に立たないためです。大きさは唯一の制約であるため、大きさは私がアクセスする必要があるすべてです。
Ungolfed ILPビルダー
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
これは、上記でリンクされた「出力例」を実際に生成したプログラムです。したがって、各グリッドの最後にある余分な長い文字列は、ゴルフをするときに切り捨てました。(ゴルフバージョンは、単語を除いた同一の出力を生成するはずです"Filled squares for "
)
使い方
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グリッドを使用し、中央の16x16部分が実際のパズルソリューションです。cells
このグリッドです。最初の行は、324個のバイナリ変数「cell_0_0」、「cell_0_1」などを作成します。また、グリッドのソリューション部分のセル間およびセルの周囲に「スペース」のグリッドを作成します。rowseps
は、セルを水平に区切るスペースを象徴する289個の変数をcolseps
指し、同様に、セルを垂直に区切るスペースをマークする変数を指します。これがユニコード図です:
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および□
sはによって追跡バイナリ値であるcell
変数|
Sは、バイナリ値によって追跡されrowsep
た変数、及び-
Sは、バイナリ値によって追跡されるcolsep
変数。
prob += sum(cells[r][c] for r in rows for c in cols),""
これが目的関数です。すべてのcell
変数の合計。これらはバイナリ変数であるため、これはソリューションの塗りつぶされた正方形の数とまったく同じです。
for i in rows:
prob += cells["0"][i] == 0,""
prob += cells[i]["0"] == 0,""
prob += cells["17"][i] == 0,""
prob += cells[i]["17"] == 0,""
これは、グリッドの外縁の周りのセルをゼロに設定するだけです(そのため、上記のセルをゼロとして表しています)。これは、セルの「ブロック」がいくつ埋められたかを追跡する最も便利な方法です。これは、未記入から記入へのすべての変更(列または行の移動)が、記入から未記入への対応する変更(およびその逆)と一致することを保証するためです)、行の最初または最後のセルが埋められた場合でも。これが、最初に18x18グリッドを使用する唯一の理由です。ブロックを数える唯一の方法ではありませんが、最も簡単だと思います。
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,""
これがILPのロジックの本質です。基本的に、各セル(最初の行と列のセルを除く)は、セルの論理xorであり、行の左に直接あり、列のすぐ上にあります。この素晴らしい答えから、{0,1}整数プログラム内のxorをシミュレートする制約を取得しました:https : //cs.stackexchange.com/a/12118/44289
もう少し説明すると、このxor制約は、0と1のセルの間にある場合にのみセパレーターを1にできるようにします(未入力から入力へ、またはその逆への変更をマーク)。したがって、行または列の1値の区切り文字は、その行または列のブロック数のちょうど2倍になります。つまり、特定の行または列の区切り文字の合計は、その行/列の大きさのちょうど2倍です。したがって、次の制約があります。
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,""
そしてそれはほとんどそれです。残りは、デフォルトのソルバーにILPを解決するように要求し、結果のソリューションをファイルに書き込むときにフォーマットします。