पायथन 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
यह पता चलता है कि जैसे समस्याएं आसानी से पूर्णांक कार्यक्रम के लिए परिवर्तनीय हैं, और मुझे एलएल सॉल्वर की एक किस्म के लिए PuLP- एक अजगर इंटरफ़ेस का उपयोग करने के लिए सीखने की एक बुनियादी समस्या की आवश्यकता थी - मेरी खुद की एक परियोजना के लिए। यह भी पता चला है कि PuLP उपयोग करने के लिए बेहद आसान है, और ungolfed LP बिल्डर ने पूरी तरह से पहली बार काम किया जब मैंने इसे आज़माया।
एक शाखा-और-बाउंड आईपी सॉल्वर को नियोजित करने के बारे में दो अच्छी बातें मेरे लिए इसे सुलझाने का कठिन काम करने के लिए (एक शाखा और बाध्य सॉल्वर को लागू नहीं करने के अलावा) हैं कि
- उद्देश्य-निर्मित सॉल्वर वास्तव में तेज़ हैं। यह कार्यक्रम मेरे अपेक्षाकृत कम अंत वाले होम पीसी पर लगभग 50000 समस्याओं को हल करता है। प्रत्येक उदाहरण को हल करने में 1-1.5 सेकंड का समय लगा।
- वे गारंटीकृत इष्टतम समाधान का उत्पादन करते हैं (या आपको बताते हैं कि वे ऐसा करने में विफल रहे)। इस प्रकार, मुझे विश्वास हो सकता है कि कोई भी मेरे स्कोर को वर्गों में नहीं हराएगा (हालाँकि कोई इसे बाँध सकता है और मुझे गोल्फ भाग पर हरा सकता है)।
इस कार्यक्रम का उपयोग कैसे करें
सबसे पहले, आपको PuLP इंस्टॉल करना होगा। pip install pulp
यदि आपने पाइप स्थापित किया है तो चाल चलनी चाहिए।
फिर, आपको "c" नामक एक फ़ाइल में निम्नलिखित डालने की आवश्यकता होगी: https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=singing
फिर, एक ही निर्देशिका से किसी भी दिवंगत पायथन 2 बिल्ड में इस कार्यक्रम को चलाएं। एक दिन से भी कम समय में, आपके पास "s" नामक एक फ़ाइल होगी जिसमें 50,000 हल किए गए नॉनोग्राम ग्रिड (पठनीय प्रारूप में) होते हैं, जिनमें से प्रत्येक में नीचे भरे हुए कुल वर्ग होते हैं।
यदि आप इसके बजाय भरे हुए वर्गों की संख्या को अधिकतम करना चाहते हैं, तो LpMinimize
पंक्ति 8 को LpMaximize
इसके स्थान पर बदलें । आपको आउटपुट बहुत पसंद आएगा: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing
इनपुट प्रारूप
यह प्रोग्राम एक संशोधित इनपुट प्रारूप का उपयोग करता है, क्योंकि जो जेड ने कहा कि अगर हमें ओपी पर एक टिप्पणी पसंद है तो हमें इनपुट प्रारूप को फिर से एनकोड करने की अनुमति होगी। उपरोक्त लिंक पर क्लिक करके देखें कि यह कैसा दिखता है। इसमें 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
(इस पुन: एन्कोडिंग प्रोग्राम ने मुझे अपने कस्टम बिटक्वायु वर्ग का परीक्षण करने का एक अतिरिक्त अवसर भी दिया, जो मैंने ऊपर उल्लेखित एक ही परियोजना के लिए बनाया है। यह केवल एक कतार है, जिसमें डेटा को बिट्स या बाइट्स के अनुक्रम के रूप में धकेला जा सकता है, और किस डेटा से। एक समय में या तो एक बिट या बाइट को पॉपअप किया जाता है। इस उदाहरण में, यह पूरी तरह से काम करता है।)
मैंने उस विशिष्ट कारण के लिए इनपुट को फिर से एनकोड किया जो कि ILP बनाने के लिए, परिमाण उत्पन्न करने के लिए उपयोग किए जाने वाले ग्रिड के बारे में अतिरिक्त जानकारी पूरी तरह से बेकार है। परिमाण केवल बाधाएं हैं, और इसलिए परिमाण वे सभी हैं जिनकी मुझे आवश्यकता है।
अनप्लग्ड 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 बाइनरी वैरिएबल बनाती है: "सेल_0_0", "सेल_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
और □
रों ने उन्हें ट्रैक बाइनरी मान हैं 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,""
यह बस ग्रिड के बाहरी किनारे के चारों ओर की कोशिकाओं को शून्य पर सेट करता है (यही कारण है कि मैंने उन्हें ऊपर शून्य के रूप में दर्शाया है)। यह कोशिकाओं के कितने "ब्लॉक" को ट्रैक करने का सबसे समीचीन तरीका है, क्योंकि यह सुनिश्चित करता है कि अनफ़िल्टर्ड से भरा हुआ (कॉलम या पंक्ति के पार ले जाने वाला) से प्रत्येक परिवर्तन को भरे हुए (और इसके विपरीत से भरे हुए परिवर्तन से मेल खाता है) ), भले ही पंक्ति में पहला या अंतिम सेल भरा हो। यह पहली जगह में 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 के तर्क का वास्तविक मांस है। मूल रूप से यह आवश्यक है कि प्रत्येक सेल (पहली पंक्ति और स्तंभ के अलावा) सेल का तार्किक एक्सोर हो और विभाजक सीधे अपनी पंक्ति में बाईं ओर और सीधे उसके स्तंभ में ऊपर। मुझे इस अद्भुत उत्तर से {0,1} पूर्णांक कार्यक्रम के भीतर एक एक्सोर का अनुकरण करने वाली अड़चनें मिलीं: /cs//a/12118/44289
थोड़ा और समझाने के लिए: यह एक्सोर बाधा बनाता है ताकि विभाजक 1 हो सकते हैं और केवल अगर वे कोशिकाओं के बीच झूठ बोलते हैं जो 0 और 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 को हल करने के लिए कहता है, फिर परिणामी समाधान को प्रारूपित करता है क्योंकि यह फ़ाइल में लिखता है।