मैं थोड़ी देर के लिए झाड़ी के चारों ओर पिटने जा रहा हूं, लेकिन एक बिंदु है।
semigroups
इसका उत्तर है, बाइनरी रिडक्शन ऑपरेशन की सहयोगी संपत्ति ।
यह बहुत सार है, लेकिन गुणा एक अच्छा उदाहरण है। यदि x , y और z कुछ प्राकृतिक संख्याएँ हैं (या पूर्णांक, या परिमेय संख्याएँ, या वास्तविक संख्याएँ, या जटिल संख्याएँ, या N × N matrices, या पूरी तरह से अधिक कोई चीज़), तो x × y एक ही तरह का है x और y दोनों की संख्या । हमने दो नंबरों के साथ शुरू किया, इसलिए यह एक बाइनरी ऑपरेशन है, और एक मिल गया है, इसलिए हमने एक-एक करके संख्याओं की गिनती कम कर दी, जिससे यह एक कमी ऑपरेशन बन गया। और ( x × y ) × z हमेशा x × ( y × ) के समान होता हैz ), जो सहयोगी संपत्ति है।
(यदि आप पहले से ही यह सब जानते हैं, तो आप अगले अनुभाग पर जा सकते हैं।)
कंप्यूटर विज्ञान में कुछ और चीजें जो आप अक्सर देखते हैं जो उसी तरह से काम करती हैं:
- गुणा करने के बजाय किसी भी प्रकार की संख्याओं को जोड़ना
- कंक्रीटिंग स्ट्रिंग्स (
"a"+"b"+"c"
यह है "abc"
कि क्या आप के साथ शुरू "ab"+"c"
या "a"+"bc"
)
- दो सूचियों को एक साथ विभाजित करना।
[a]++[b]++[c]
इसी तरह [a,b,c]
या तो आगे से पीछे या सामने से पीछे है।
cons
सिर और पूंछ पर, यदि आप सिर को एक सिंगलटन सूची के रूप में समझते हैं। यह सिर्फ दो सूचियों को समाप्त करना है।
- संघ या चौराहों का सेट लेना
- बूलियन और, बूलियन या
- बिटवाइज़
&
, |
और^
- कार्यों की रचना: ( च ∘ जी ) ∘ ज एक्स = च ∘ ( छ ∘ ज ) एक्स = च ( जी ( ज ( एक्स )))
- अधिकतम और न्यूनतम
- इसके अलावा मोडुलो पी
कुछ चीजें जो नहीं हैं:
- घटाव, क्योंकि 1- (1-2) ≠ (1-1) -2
- एक्स ⊕ y = तन ( एक्स + y because ), क्योंकि tan (4/4 / 4) अपरिभाषित है
- नकारात्मक संख्याओं पर गुणन, क्योंकि -1 × -1 ऋणात्मक संख्या नहीं है
- पूर्णांकों का विभाजन, जिसमें तीनों समस्याएं हैं!
- तार्किक नहीं, क्योंकि इसमें केवल एक ऑपरेंड है, दो नहीं
int print2(int x, int y) { return printf( "%d %d\n", x, y ); }
, के रूप में print2( print2(x,y), z );
और print2( x, print2(y,z) );
विभिन्न उत्पादन किया है।
यह एक उपयोगी पर्याप्त अवधारणा है जिसे हमने नाम दिया है। एक ऑपरेशन के साथ एक सेट जिसमें ये गुण हैं, एक अर्धवृत्त है । तो, गुणन के अंतर्गत वास्तविक संख्या एक अर्धसमूह है। और आपका प्रश्न उन तरीकों में से एक है जो इस तरह के अमूर्त वास्तविक दुनिया में उपयोगी हो जाते हैं। सेमीग्रुप संचालन को आप जिस तरह से पूछ रहे हैं उसके अनुसार सभी को अनुकूलित किया जा सकता है।
घर पर यह कोशिश करो
जहां तक मुझे पता है, इस तकनीक का वर्णन पहली बार 1974 में डैनियल फ्रीडमैन और डेविड वाइज के पेपर में किया गया था, "फोल्डिंग स्टाइलाइज्ड रिकर्सिएशन इन इटरेशन्स" , हालांकि उन्होंने कुछ और गुणों को ग्रहण किया, तुलना में उन्हें इसकी आवश्यकता थी।
हास्केल में यह वर्णन करने के लिए एक शानदार भाषा है, क्योंकि Semigroup
इसके मानक पुस्तकालय में टाइपकास्ट है। यह एक सामान्य Semigroup
ऑपरेटर के संचालन को कहता है <>
। चूँकि सूचियाँ और तार उदाहरण हैं Semigroup
, इसलिए उनके उदाहरण उदाहरण के <>
तौर पर संघटक संचालक के रूप में परिभाषित होते हैं ++
। और सही आयात के साथ, के [a] <> [b]
लिए एक उपनाम है [a] ++ [b]
, जो है[a,b]
।
लेकिन, संख्या के बारे में क्या? हम सिर्फ देखा कि सांख्यिक प्रकार के तहत semigroups हैं या तो इसके या गुणा! तो जो एक के लिए हो जाता <>
है Double
? खैर, या तो एक! हास्केल प्रकार परिभाषित करता है Product Double
, where (<>) = (*)
(जो हास्केल में वास्तविक परिभाषा है), और भी Sum Double
, where (<>) = (+)
।
एक शिकन यह है कि आपने इस तथ्य का उपयोग किया है कि 1 गुणात्मक पहचान है। एक पहचान के साथ एक semigroup एक कहा जाता है monoid और हास्केल पैकेज में परिभाषित किया गया है Data.Monoid
, जो एक typeclass के जेनेरिक पहचान तत्व कॉल mempty
। Sum
, Product
और प्रत्येक के पास एक पहचान तत्व (0, 1 और []
क्रमशः) है, इसलिए वे उदाहरण Monoid
भी हैं Semigroup
। (एक सन्यासी के साथ भ्रमित होने की नहीं , इसलिए मुझे भूल जाओ मैं भी उन लोगों को लाया।)
आपके एल्गोरिथ्म को मोनोसेक का उपयोग करके अपने एल्गोरिथ्म का अनुवाद करने के लिए पर्याप्त जानकारी है:
module StylizedRec (pow) where
import Data.Monoid as DM
pow :: Monoid a => a -> Word -> a
{- Applies the monoidal operation of the type of x, whatever that is, by
- itself n times. This is already in Haskell as Data.Monoid.mtimes, but
- let’s write it out as an example.
-}
pow _ 0 = mempty -- Special case: Return the nullary product.
pow x 1 = x -- The base case.
pow x n = x <> (pow x (n-1)) -- The recursive case.
महत्वपूर्ण रूप से, ध्यान दें कि यह पूंछ पुनरावर्तन मॉडुलो सेमिनग्रुप है: हर मामला या तो एक मूल्य है, एक पूंछ-पुनरावर्ती कॉल या दोनों का सेमीग्रुप उत्पाद है। इसके अलावा, यह उदाहरण mempty
मामलों में से एक के लिए उपयोग करने के लिए हुआ था, लेकिन अगर हमें इसकी आवश्यकता नहीं थी, तो हम इसे अधिक टाइपकास्ट के साथ कर सकते थेSemigroup
।
आइए इस कार्यक्रम को GHCI में लोड करें और देखें कि यह कैसे काम करता है:
*StylizedRec> getProduct $ pow 2 4
16
*StylizedRec> getProduct $ pow 7 2
49
याद रखें कि हमने pow
एक सामान्य के लिए Monoid
किस प्रकार की घोषणा की , जिसे हमने टाइप किया a
? हम निकालना GHCi पर्याप्त जानकारी दी है कि प्रकार a
यहाँ है Product Integer
, एक है जो instance
की Monoid
जिसका <>
संचालन पूर्णांक गुणा है। इसलिए pow 2 4
पुनरावृत्ति का विस्तार करता है 2<>2<>2<>2
, जो है 2*2*2*2
या16
। अब तक सब ठीक है।
लेकिन हमारा कार्य केवल सामान्य मोनोइड ऑपरेशन का उपयोग करता है। पहले, मैंने कहा कि Monoid
कॉल का एक और उदाहरण है Sum
, जिसका <>
ऑपरेशन है +
। क्या हम ऐसा प्रयास कर सकते हैं?
*StylizedRec> getSum $ pow 2 4
8
*StylizedRec> getSum $ pow 7 2
14
वही विस्तार अब हमें 2+2+2+2
इसके बजाय देता है 2*2*2*2
। गुणन को जोड़ना है क्योंकि घातांक गुणा करना है!
लेकिन मैंने हास्केल मोनॉयड का एक और उदाहरण दिया: सूचियां, जिनका संचालन संघनन है।
*StylizedRec> pow [2] 4
[2,2,2,2]
*StylizedRec> pow [7] 2
[7,7]
लेखन [2]
कंपाइलर को बताता है कि यह एक सूची है, <>
सूचियों पर है ++
, ऐसा [2]++[2]++[2]++[2]
है [2,2,2,2]
।
अंत में, एक एल्गोरिथम (दो, तथ्य में)
बस के x
साथ बदलने से [x]
, आप सामान्य एल्गोरिथ्म को परिवर्तित करते हैं जो पुनरावर्तन मोड्यूलो का उपयोग करता है जो एक सूची बनाता है। कौन सी सूची? एल्गोरिथ्म पर लागू <>
होने वाले तत्वों की सूची । चूँकि हमने केवल वही सेग्रिगॉप ऑपरेशंस का उपयोग किया है जो सूचियों में भी है, परिणामी सूची मूल गणना के लिए आइसोमॉर्फिक होगी। और चूंकि मूल ऑपरेशन साहचर्य था, इसलिए हम समान रूप से पीछे से आगे या पीछे से तत्वों का समान रूप से मूल्यांकन कर सकते हैं।
यदि आपका एल्गोरिथ्म कभी बेस केस तक पहुंचता है और समाप्त हो जाता है, तो सूची गैर-रिक्त होगी। चूंकि टर्मिनल केस ने कुछ वापस किया, इसलिए यह सूची का अंतिम तत्व होगा, इसलिए इसमें कम से कम एक तत्व होगा।
आप क्रम में किसी सूची के प्रत्येक तत्व पर बाइनरी रिडक्शन ऑपरेशन कैसे लागू करते हैं? यह सही है, एक गुना। तो अगर आप स्थानापन्न कर सकते हैं [x]
के लिए x
, द्वारा कम करने के लिए तत्वों की सूची प्राप्त <>
करने और फिर सही गुना या सूची छोड़ दिया गुना:
*StylizedRec> getProduct $ foldr1 (<>) $ pow [Product 2] 4
16
*StylizedRec> import Data.List
*StylizedRec Data.List> getProduct $ foldl1' (<>) $ pow [Product 2] 4
16
संस्करण foldr1
वास्तव में मानक पुस्तकालय में मौजूद है, जैसा sconcat
कि Semigroup
और इसके mconcat
लिए Monoid
। यह सूची पर एक आलसी राइट फोल्ड करता है। यही है, यह करने के [Product 2,Product 2,Product 2,Product 2]
लिए फैलता है 2<>(2<>(2<>(2)))
।
यह इस मामले में कुशल नहीं है क्योंकि आप व्यक्तिगत शर्तों के साथ कुछ भी नहीं कर सकते हैं जब तक कि आप उन सभी को उत्पन्न नहीं करते हैं। (एक बिंदु पर मैंने यहां चर्चा की थी कि दाएं सिलवटों का उपयोग कब करना है और कब बाएं परतों का उपयोग करना है, लेकिन यह बहुत दूर चला गया।)
जिस संस्करण के साथ foldl1'
सख्ती से मूल्यांकन किया गया है, वह बाएं गुना है। यह कहना है, एक सख्त संचायक के साथ एक पूंछ-पुनरावर्ती कार्य। यह इसका मूल्यांकन करता है (((2)<>2)<>2)<>2
, जब आवश्यक हो तुरंत और बाद में गणना की जाती है। (कम से कम, फोल्ड के भीतर कोई देरी नहीं होती है: फोल्ड की जा रही सूची यहां एक अन्य फ़ंक्शन द्वारा उत्पन्न की जाती है जो आलसी मूल्यांकन हो सकता है।) इसलिए, फोल्ड गणना करता है (4<>2)<>2
, फिर तुरंत गणना करता है 8<>2
, फिर।16
। यही कारण है कि हमें ऑपरेशन के लिए साहचर्य की आवश्यकता थी: हमने सिर्फ कोष्ठकों के समूह को बदल दिया!
सख्त बाएं गुना जीसीसी क्या कर रहा है के बराबर है। पिछले उदाहरण में सबसे बाईं संख्या संचायक है, इस मामले में एक चल उत्पाद। प्रत्येक चरण में, यह सूची में अगले नंबर से गुणा किया जाता है। इसे व्यक्त करने का दूसरा तरीका यह है: आप मूल्यों को गुणा करने के लिए पुनरावृत्ति करते हैं, चल रहे उत्पाद को एक संचायक में रखते हैं, और प्रत्येक पुनरावृत्ति पर, आप अगले मान द्वारा संचायक को गुणा करते हैं। यही है, यह while
भेस में एक पाश है।
इसे कभी-कभी केवल कुशल बनाया जा सकता है। संकलक स्मृति में सूची डेटा संरचना को दूर करने में सक्षम हो सकता है। सिद्धांत रूप में, यह संकलित करने के लिए पर्याप्त समय पर पर्याप्त जानकारी है कि इसे यहां करना चाहिए: [x]
एक सिंगलटन है, इसलिए [x]<>xs
जैसा है वैसा ही है cons x xs
। फ़ंक्शन का प्रत्येक पुनरावृत्ति समान स्टैक फ़्रेम का फिर से उपयोग करने और जगह में मापदंडों को अपडेट करने में सक्षम हो सकता है।
किसी विशेष मामले में या तो दायां गुना या सख्त बायां गुना अधिक उपयुक्त हो सकता है, इसलिए पता करें कि आपको कौन सा चाहिए। कुछ चीजें भी हैं जो केवल एक सही तह ही कर सकती हैं (जैसे कि सभी इनपुट की प्रतीक्षा किए बिना इंटरैक्टिव आउटपुट उत्पन्न करें, और अनंत सूची पर काम करें)। यहाँ, हालांकि, हम एक साधारण मूल्य पर परिचालन के अनुक्रम को कम कर रहे हैं, इसलिए एक सख्त बाएं गुना वह है जो हम चाहते हैं।
इसलिए, जैसा कि आप देख सकते हैं, टेल-रिकर्सन मोड्यूलो को स्वचालित रूप से अनुकूलित करना संभव है, किसी भी सेमीग्रुप (जिसका एक उदाहरण गुणन के तहत सामान्य संख्यात्मक प्रकारों में से कोई है) या तो एक आलसी दाएं गुना या एक सख्त बाएं गुना, एक लाइन में हास्केल।
आगे का सामान्यीकरण
बाइनरी ऑपरेशन के दो तर्कों को एक ही प्रकार का होना जरूरी नहीं है, इसलिए जब तक प्रारंभिक मूल्य आपके परिणाम के समान है। (आप निश्चित रूप से उस तर्क को फ्लिप कर सकते हैं जो आप कर रहे हैं, बाएं या दाएं तरह के गुना के क्रम से मिलान करने के लिए।) इसलिए आप बार-बार एक अपडेट की गई फ़ाइल प्राप्त करने के लिए एक फ़ाइल में पैच जोड़ सकते हैं, या प्रारंभिक मूल्य के साथ शुरू कर सकते हैं। 1.0, एक फ्लोटिंग-पॉइंट परिणाम जमा करने के लिए पूर्णांकों द्वारा विभाजित करें। या सूची प्राप्त करने के लिए खाली सूची में तत्वों को प्रीपेन्ड करें।
एक अन्य प्रकार का सामान्यीकरण सिलवटों को सूचियों में नहीं बल्कि अन्य Foldable
डेटा संरचनाओं पर लागू करना है। अक्सर, एक अपरिवर्तनीय रैखिक लिंक्ड सूची डेटा संरचना नहीं है जिसे आप किसी दिए गए एल्गोरिदम के लिए चाहते हैं। एक मुद्दा जो मुझे ऊपर नहीं मिला, वह यह है कि पीछे की तुलना में सूची के सामने तत्वों को जोड़ने के लिए यह बहुत अधिक कुशल है, और जब ऑपरेशन सराहनीय नहीं है, x
तो बाईं ओर आवेदन करना और ऑपरेशन का अधिकार नहीं है वही। तो आपको एक अन्य संरचना का उपयोग करने की आवश्यकता होगी, जैसे कि सूचियों की एक जोड़ी या बाइनरी ट्री, एक एल्गोरिथ्म का प्रतिनिधित्व करने के लिए x
जो दाईं ओर और बाईं ओर लागू हो सकती है <>
।
यह भी ध्यान दें कि सहयोगी संपत्ति आपको अन्य उपयोगी तरीकों से संचालन को फिर से व्यवस्थित करने की अनुमति देती है, जैसे कि विभाजन और जीत:
times :: Monoid a => a -> Word -> a
times _ 0 = mempty
times x 1 = x
times x n | even n = y <> y
| otherwise = x <> y <> y
where y = times x (n `quot` 2)
या स्वचालित समानतावाद, जहां प्रत्येक थ्रेड एक मान को कम करता है जो बाद में दूसरों के साथ संयुक्त होता है।
if(n==0) return 0;
(आपके प्रश्न में 1 रिटर्न की तरह नहीं है)।x^0 = 1
, तो वह एक बग है। ऐसा नहीं है कि यह बाकी सवाल के लिए मायने रखता है, हालांकि; पुनरावृति asm उस विशेष मामले के लिए पहले जाँच करता है। लेकिन अजीब बात है, पुनरावृत्ति कार्यान्वयन एक बहुतायत का परिचय1 * x
देता है जो स्रोत में मौजूद नहीं था, भले ही हम एकfloat
संस्करण बनाते हैं । gcc.godbolt.org/z/eqwine (और gcc केवल साथ सफल होता है-ffast-math
।)