जीएनयू प्रोलॉग, 98 बाइट्स
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
यह उत्तर इस बात का एक बड़ा उदाहरण है कि कैसे प्रोलॉग सबसे सरल I / O स्वरूपों के साथ संघर्ष कर सकता है। यह प्रॉब्लम को हल करने के लिए एल्गोरिदम के बजाय समस्या का वर्णन करके सही प्रोलॉग शैली में काम करता है: यह निर्दिष्ट करता है कि कानूनी बबल व्यवस्था के रूप में क्या मायने रखता है, प्रोलॉग को उन सभी बबल व्यवस्था को उत्पन्न करने के लिए कहता है, और फिर उन्हें गिनता है। पीढ़ी 55 वर्ण (कार्यक्रम की पहली दो पंक्तियाँ) लेती है। गिनती और I / O अन्य 43 (तीसरी पंक्ति, और दो भागों को अलग करने वाली नई पंक्ति) लेता है। मुझे यकीन है कि यह एक समस्या नहीं है कि ओपी ने I / O के साथ संघर्ष करने के लिए भाषाओं का कारण बनने की उम्मीद की है! (नोट: स्टैक एक्सचेंज का सिंटैक्स हाइलाइटिंग पढ़ने के लिए कठिन है, आसान नहीं है, इसलिए मैंने इसे बंद कर दिया)।
व्याख्या
आइए एक समान कार्यक्रम के छद्म संस्करण के साथ शुरू करें जो वास्तव में काम नहीं करता है:
b(Bubbles,Count) if map(b,Bubbles,BubbleCounts)
and sum(BubbleCounts,InteriorCount)
and Count is InteriorCount + 1
and is_sorted(Bubbles).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
यह स्पष्ट रूप से स्पष्ट होना चाहिए कि कैसे b
काम करता है: हम छंटनी सूचियों के माध्यम से बुलबुले का प्रतिनिधित्व कर रहे हैं (जो मल्टीसेट का एक सरल कार्यान्वयन है जो समान मल्टीसीट को बराबर की तुलना करता है), और एक बुलबुले []
में 1 की गिनती होती है, जिसमें एक बड़ा बुलबुला होता है। प्लस 1 के अंदर बुलबुले की कुल संख्या के बराबर 1. 4 की गिनती के लिए, यह कार्यक्रम होगा (यदि यह काम किया) निम्नलिखित सूचियां उत्पन्न करेगा:
[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]
यह कार्यक्रम कई कारणों से एक उत्तर के रूप में अनुपयुक्त है, लेकिन सबसे अधिक दबाव यह है कि प्रोलॉग वास्तव में एक map
विधेय नहीं है (और यह लिखना बहुत अधिक बाइट्स ले जाएगा)। इसलिए इसके बजाय, हम प्रोग्राम को इस तरह लिखते हैं:
b([], 0).
b([Head|Tail],Count) if b(Head,HeadCount)
and b(Tail,TailCount)
and Count is HeadCount + TailCount + 1
and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
यहाँ दूसरी बड़ी समस्या यह है कि जब यह प्रोलॉग के मूल्यांकन के क्रम में काम करता है, तो यह एक अनंत लूप में चला जाता है। हालाँकि, हम प्रोग्राम को थोड़ा पीछे करके अनंत लूप को हल कर सकते हैं:
b([], 0).
b([Head|Tail],Count) if Count #= HeadCount + TailCount + 1
and b(Head,HeadCount)
and b(Tail,TailCount)
and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
and length(List,NPossibilities).
यह काफी अजीब लग सकता है - हम एक साथ जोड़ रहे हैं इससे पहले कि हम जानते हैं कि वे क्या हैं - लेकिन जीएनयू #=
प्रोलॉग नॉनकौसल अंकगणित के उस प्रकार से निपटने में सक्षम है, और क्योंकि यह बहुत पहली पंक्ति है b
, और HeadCount
और TailCount
दोनों को कम होना चाहिए Count
(जो ज्ञात है), यह स्वाभाविक रूप से सीमित करने की एक विधि के रूप में कार्य करता है कि पुनरावर्ती शब्द कितनी बार मेल कर सकता है, और इस कारण कार्यक्रम हमेशा समाप्त हो सकता है।
अगला कदम यह है कि इसे थोड़ा नीचे कर दिया जाए। सफेद स्थान को निकाल, एकल चरित्र चर नाम का उपयोग करते हुए, जैसे संक्षिप्त रूपों का उपयोग कर :-
के लिए if
और ,
के लिए and
, का उपयोग कर setof
के बजाय listof
(यह एक छोटा नाम है और इस मामले में एक ही परिणाम का उत्पादन), और का उपयोग कर sort0(X,X)
के बजाय is_sorted(X)
(क्योंकि is_sorted
वास्तव में कोई वास्तविक कार्य नहीं है, मैंने इसे बनाया):
b([],0).
b([H|T],N):-N#=A+B+1,b(H,A),b(T,B),sort0([H|T],[H|T]).
c(X,Y):-setof(A,b(A,X),L),length(L,Y).
यह काफी कम है, लेकिन बेहतर करना संभव है। महत्वपूर्ण जानकारी यह है कि [H|T]
सूची सिंटैक्स के रूप में वास्तव में क्रिया है। जैसा कि लिस्प प्रोग्रामर्स को पता होगा, एक सूची मूल रूप से सिर्फ कॉन्स कोशिकाओं से बनी है, जो मूल रूप से सिर्फ ट्यूपल हैं, और शायद ही इस कार्यक्रम का कोई भी हिस्सा लिस्ट बिलिन का उपयोग कर रहा है। प्रोलॉग के पास बहुत कम टुपल सिंटैक्स हैं (मेरा पसंदीदा है A-B
, लेकिन मेरा दूसरा पसंदीदा है A/B
, जो मैं यहां उपयोग कर रहा हूं क्योंकि यह इस मामले में आसानी से पढ़ा जाने वाला डिबग आउटपुट पैदा करता है); और हम nil
सूची के अंत के लिए अपना एकल-चरित्र भी चुन सकते हैं , बजाय दो-चरित्र के साथ अटक जाने के []
(मैंने चुना x
, लेकिन मूल रूप से कुछ भी काम करता है)। इसलिए इसके बजाय [H|T]
, हम इसका उपयोग कर सकते हैं T/H
, और आउटपुट प्राप्त कर सकते हैंb
यह इस तरह दिखता है (ध्यान दें कि ट्यूल पर क्रमबद्ध क्रम सूची से थोड़ा अलग है, इसलिए ये ऊपर दिए गए क्रम में नहीं हैं):
x/x/x/x/x
x/x/x/(x/x)
x/(x/x)/(x/x)
x/x/(x/x/x)
x/(x/x/x/x)
x/x/(x/(x/x))
x/(x/x/(x/x))
x/(x/(x/x/x))
x/(x/(x/(x/x)))
यह उपर्युक्त नेस्टेड सूचियों की तुलना में पढ़ना कठिन है, लेकिन यह संभव है; मानसिक रूप से एस को छोड़ दें x
, और /()
एक बुलबुले के रूप में व्याख्या करें (या /
बिना किसी सामग्री के साथ एक पतले बुलबुले के रूप में सादे , अगर इसके ()
बाद कोई नहीं है), और तत्वों में 1 से लेकर 1 (यदि अव्यवस्थित) के ऊपर दिखाए गए सूची संस्करण के साथ पत्राचार है ।
बेशक, यह सूची प्रतिनिधित्व, बहुत कम होने के बावजूद, एक बड़ी खामी है; यह भाषा में नहीं बनाया गया है, इसलिए हम यह sort0
जांचने के लिए उपयोग नहीं कर सकते हैं कि हमारी सूची को क्रमबद्ध किया गया है या नहीं। sort0
वैसे भी काफी वाचालता है, हालांकि, इसे हाथ से करना बहुत बड़ा नुकसान नहीं है (वास्तव में, [H|T]
सूची प्रतिनिधित्व पर हाथ से ऐसा करना बिल्कुल उसी बाइट्स के लिए आता है)। यहां महत्वपूर्ण अंतर्दृष्टि यह है कि सूची को क्रमबद्ध करने के लिए लिखित चेकों के रूप में कार्यक्रम, यदि इसकी पूंछ को क्रमबद्ध किया गया है, यदि इसकी पूंछ को क्रमबद्ध किया गया है, और इसी तरह; बहुत सारे निरर्थक चेक हैं, और हम इसका फायदा उठा सकते हैं। इसके बजाय, हम केवल यह सुनिश्चित करने के लिए जाँच करेंगे कि पहले दो तत्व क्रम में हैं (जो यह सुनिश्चित करता है कि सूची समाप्त हो जाएगी एक बार सूची और उसके सभी प्रत्ययों की जाँच हो जाएगी)।
पहला तत्व आसानी से सुलभ है; यह सिर्फ सूची का प्रमुख है H
। दूसरा तत्व पहुंच के लिए कठिन है, हालांकि, और मौजूद नहीं हो सकता है। सौभाग्य से, x
उन सभी ट्यूपलों से कम है जिन्हें हम विचार कर रहे हैं (प्रोलॉग के सामान्यीकृत तुलना ऑपरेटर के माध्यम से @>=
), इसलिए हम एक सिंगलटन सूची के "दूसरे तत्व" पर विचार कर सकते हैं x
और प्रोग्राम ठीक काम करेगा। जैसा कि वास्तव में दूसरे तत्व तक पहुंचने के लिए, सबसे कठिन तरीका तीसरा तर्क (एक आउट तर्क) जोड़ना है b
, जो x
आधार मामले H
में और पुनरावर्ती मामले में वापस आता है; इसका मतलब है कि हम दूसरी पुनरावर्ती कॉल से आउटपुट के रूप में पूंछ के सिर को पकड़ सकते हैं B
, और निश्चित रूप से पूंछ का सिर सूची का दूसरा तत्व है। अब b
ऐसा दिखता है:
b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
आधार मामला काफी सरल है (खाली सूची, 0 की गिनती लौटाएं, खाली सूची का "पहला तत्व" है x
)। पुनरावर्ती मामला पहले की तरह ही शुरू हो जाता है (केवल T/H
नोटेशन के साथ [H|T]
, और H
अतिरिक्त तर्क के रूप में); हम सिर पर पुनरावर्ती कॉल से अतिरिक्त तर्क की उपेक्षा करते हैं, लेकिन इसे J
पूंछ पर पुनरावर्ती कॉल में संग्रहीत करते हैं। फिर हमें केवल यह सुनिश्चित करना है कि सूची से कम H
या अधिक J
(यानी "यदि सूची में कम से कम दो तत्व हैं, तो यह सुनिश्चित करने के लिए कि सूची समाप्त हो गई है)
दुर्भाग्य से, setof
अगर हम c
इस नई परिभाषा के साथ एक साथ पिछली परिभाषा का उपयोग करने का प्रयास करते हैं, तो एक फिट फेंकता है b
, क्योंकि यह अप्रयुक्त मापदंडों को SQL के समान कम या ज्यादा तरीके से व्यवहार करता है GROUP BY
, जो पूरी तरह से वह नहीं है जो हम चाहते हैं। हम जो चाहते हैं, उसे करने के लिए इसे फिर से कॉन्फ़िगर करना संभव है, लेकिन उस पुन: निर्माण में पात्रों की लागत होती है। इसके बजाय, हम उपयोग करते हैं findall
, जिसमें एक अधिक सुविधाजनक डिफ़ॉल्ट व्यवहार होता है और यह केवल दो अक्षर लंबा है, जिससे हमें यह परिभाषा मिलती है c
:
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).
और वह पूरा कार्यक्रम है; tersely बुलबुला पैटर्न उत्पन्न करते हैं, फिर उन्हें गिनते हुए बाइट्स का एक पूरा भार खर्च करते हैं ( findall
जनरेटर को किसी सूची में बदलने के लिए हमें लंबे समय की आवश्यकता होती है , फिर दुर्भाग्य length
से उस सूची की लंबाई की जांच करने के लिए नाम दिया गया है, और एक फ़ंक्शन के लिए बॉयलरप्लेट)।