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
(NB : 많이 들여 쓴 줄은 공백이 아닌 탭으로 시작합니다.)
출력 예 : https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
같은 문제는 Integer Linear Programs로 쉽게 변환 할 수 있으며, 내 자신의 프로젝트에 PuLP (다양한 LP 솔버를위한 파이썬 인터페이스)를 사용하는 방법을 배우는 데 기본적인 문제가 필요했습니다. 또한 PuLP는 사용하기가 매우 쉽고, ungolfed LP 빌더는 처음 시도했을 때 완벽하게 작동했습니다.
분기 및 바운드 IP 솔버를 사용하여 나를 위해이 문제를 해결하기 위해 열심히 노력하는 두 가지 좋은 점은 (분기 및 바운드 솔버를 구현하지 않아도 됨)
- 특수 목적 솔버는 정말 빠릅니다. 이 프로그램은 상대적으로 저렴한 가정용 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 빌드로이 프로그램을 실행하십시오. 하루도 채 안되어서, "s"라는 파일이 있으며, 여기에는 각각 아래에 나열된 총 채워진 사각형 수가있는 50,000 개의 해결 된 노노 그램 그리드 (판독 가능한 형식)가 들어 있습니다.
채워진 사각형의 수를 최대화하려면 대신 LpMinimize
8 행을 대신으로 변경하십시오 LpMaximize
. https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing 과 같은 출력이 표시됩니다.
입력 형식
이 프로그램은 수정 된 입력 형식을 사용합니다. Joe Z.는 OP에 대한 의견이 있으면 입력 형식을 다시 인코딩 할 수 있다고 말했습니다. 모양을 보려면 위의 링크를 클릭하십시오. 각각 16 개의 숫자를 포함하는 10000 개의 줄로 구성됩니다. 짝수 번째 줄은 주어진 인스턴스의 행에 대한 크기이고 홀수 번째 줄은 위의 행과 같은 인스턴스의 열에 대한 크기입니다. 이 파일은 다음 프로그램에 의해 생성되었습니다.
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 클래스를 테스트 할 수있는 추가 기회를 제공했습니다. 단순히 비트 OR 바이트 시퀀스로 데이터를 푸시 할 수있는 큐입니다. 한 번에 비트 또는 바이트로 표시됩니다 (이 경우 완벽하게 작동 함).
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)
나는 중심 16x16 부분이 실제 퍼즐 솔루션 인 18x18 그리드를 사용합니다. 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
변수 상기 |
의 이진 값에 의해 추적 rowsep
변수와 -
의 이진 값에 의해 추적 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,""
이것은 그리드의 바깥 가장자리 주위의 셀을 0으로 설정합니다 (위의 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를 시뮬레이트하는 제약 조건을 얻었습니다. /cs//a/12118/44289
좀 더 설명하기 위해 :이 xor 제약 조건은 구분 기호가 0과 1 인 셀 사이에있는 경우에만 구분 기호가 1이되도록합니다 (채움이 채워지지 않은 상태에서 채워진 상태로 또는 그 반대로 변경 표시). 따라서 행 또는 열에 해당 행 또는 열의 블록 수보다 정확히 두 배 많은 1 값 구분 기호가 있습니다. 다시 말해, 주어진 행 또는 열의 구분 기호의 합은 해당 행 / 열의 크기의 정확히 두 배입니다. 따라서 다음과 같은 제약 조건이 있습니다.
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를 풀도록 요청한 다음 결과 솔루션을 파일에 쓸 때 형식을 지정합니다.