algorithm - गपत - सरल समीकरण वर्ग 7




समानता के साथ समीकरण(अभिव्यक्ति) पार्सर? (15)

कठिन मार्ग

आप एक रिकर्सिव वंश पार्सर चाहते हैं।

प्राथमिकता प्राप्त करने के लिए आपको पुनरावर्ती सोचने की आवश्यकता है, उदाहरण के लिए, अपने नमूना स्ट्रिंग का उपयोग करके,

1+11*5

इसे मैन्युअल रूप से करने के लिए, आपको 1 को पढ़ना होगा, फिर प्लस देखें और 11 से शुरू होने वाला एक नया नया रिकर्सिव पार्स "सत्र" शुरू करें ... और सुनिश्चित करें कि 11 * 5 को अपने कारक में पार्स करना, एक पार्स देना पेड़ 1 + (11 * 5)

यह सब समझाने की कोशिश करने के लिए भी बहुत दर्दनाक लगता है, विशेष रूप से सी की अतिरिक्त शक्तिहीनता के साथ, देखें, 11 को पार्स करने के बाद, यदि वास्तव में * वास्तव में एक + था, तो आपको एक शब्द बनाने के प्रयास को त्यागना होगा और इसके बजाय पार्स करना होगा 11 खुद को एक कारक के रूप में। मेरा सिर पहले ही विस्फोट कर रहा है। यह रिकर्सिव सभ्य रणनीति के साथ संभव है, लेकिन एक बेहतर तरीका है ...

आसान (दाएं) रास्ता

यदि आप बाइसन जैसे जीपीएल टूल का उपयोग करते हैं, तो आपको शायद लाइसेंसिंग मुद्दों के बारे में चिंता करने की आवश्यकता नहीं है क्योंकि बाइसन द्वारा उत्पन्न सी कोड जीपीएल (आईएएनएएलएल) द्वारा कवर नहीं किया गया है, लेकिन मुझे पूरा यकीन है कि जीपीएल उपकरण जीपीएल को मजबूर नहीं करते हैं जेनरेट कोड / बाइनरी; उदाहरण के लिए ऐप्पल कोड जैसे संकलन करता है, जीसीसी के साथ एपर्चर और वे इसे जीपीएल कोड के बिना बेचते हैं)।

बाइसन डाउनलोड करें (या समकक्ष कुछ, एएनटीएलआर, आदि)।

आमतौर पर कुछ नमूना कोड होता है जिसे आप केवल बाइसन चला सकते हैं और अपना वांछित सी कोड प्राप्त कर सकते हैं जो इस चार फ़ंक्शन कैलकुलेटर को प्रदर्शित करता है:

http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html

जेनरेट कोड देखें, और देखें कि यह उतना आसान नहीं है जितना लगता है। इसके अलावा, बाइसन जैसे टूल का उपयोग करने के फायदे 1 हैं) आप कुछ सीखते हैं (विशेष रूप से यदि आप ड्रैगन पुस्तक पढ़ते हैं और व्याकरण के बारे में जानें), 2) आप पहिया को फिर से शुरू करने की कोशिश कर रहे NIH से बचें। एक वास्तविक पार्सर-जेनरेटर टूल के साथ, आपको वास्तव में बाद में स्केल करने की उम्मीद है, अन्य लोगों को दिखाते हुए कि आप जानते हैं कि पार्सर्स पार्सिंग टूल का डोमेन हैं।

अद्यतन करें:

यहां लोगों ने बहुत अच्छी सलाह दी है। पार्सिंग टूल्स को छोड़ने या सिर्फ शंटिंग यार्ड एल्गोरिदम या हाथ से लुढ़का हुआ रिकर्सिव सभ्य पार्सर का उपयोग करने के खिलाफ मेरी एकमात्र चेतावनी यह है कि छोटी खिलौना भाषाएं 1 दिन किसी दिन वास्तविक वास्तविक भाषाओं में काम (पाप, कॉस, लॉग) और चर, शर्तों और लूप।

फ्लेक्स / बाइसन बहुत छोटे, सरल दुभाषिया के लिए बहुत अधिक हो सकता है, लेकिन पार्सर + मूल्यांकनकर्ता से एक को लाइन में परेशानी हो सकती है जब परिवर्तन की आवश्यकता होती है या सुविधाओं को जोड़ने की आवश्यकता होती है। आपकी स्थिति अलग-अलग होगी और आपको अपने फैसले का उपयोग करने की आवश्यकता होगी; बस 11 [2] और पर्याप्त उपकरण से कम बनाएं।

पार्सिंग के लिए मेरा पसंदीदा टूल

नौकरी के लिए दुनिया का सबसे अच्छा टूल Parsec लाइब्रेरी (रिकर्सिव सभ्य पार्सर्स के लिए) है जो प्रोग्रामिंग भाषा हास्केल के साथ आता है। यह BNF तरह दिखता है, या पार्सिंग (नमूना कोड [3]) के लिए कुछ विशेष उपकरण या डोमेन विशिष्ट भाषा की तरह दिखता है, लेकिन वास्तव में यह हास्केल में एक नियमित पुस्तकालय है, जिसका अर्थ यह है कि यह उसी निर्माण चरण में बाकी के रूप में संकलित करता है अपने हास्केल कोड का, और आप मनमानी हास्केल कोड लिख सकते हैं और अपने पार्सर के भीतर कॉल कर सकते हैं, और आप एक ही कोड में अन्य पुस्तकालयों को मिलाकर मैच कर सकते हैं। (हास्केल के अलावा किसी अन्य भाषा में इस तरह की एक पार्सिंग भाषा को एम्बेड करना, जिस तरह से सिंटैक्टिक क्रुफ्ट के भार में परिणाम होता है। मैंने इसे सी # में किया और यह काफी अच्छा काम करता है लेकिन यह इतना सुंदर और संक्षिप्त नहीं है।)

टिप्पणियाँ:

1 रिचर्ड स्टॉलमैन कहते हैं, आपको टीसीएल का उपयोग क्यों नहीं करना चाहिए

Emacs का मुख्य सबक यह है कि एक्सटेंशन के लिए एक भाषा केवल "विस्तार भाषा" नहीं होनी चाहिए। यह एक वास्तविक प्रोग्रामिंग भाषा होनी चाहिए, जो पर्याप्त कार्यक्रमों को लिखने और बनाए रखने के लिए डिज़ाइन की गई है। क्योंकि लोग ऐसा करना चाहते हैं!

[2] हां, मैं हमेशा "भाषा" का उपयोग करने से डरा हुआ हूं।

यह भी ध्यान रखें कि जब मैंने इस प्रविष्टि को सबमिट किया था, तो पूर्वावलोकन सही था, लेकिन एसओ के पर्याप्त पैरासर से पहले के पैराग्राफ पर मेरे करीबी एंकर टैग को खाया गया था , यह साबित कर रहा था कि पार्सर्स को कुछ नहीं किया जा सकता है क्योंकि यदि आप रेगेक्स का उपयोग करते हैं और एक हैक आपको बंद करते हैं शायद कुछ सूक्ष्म और छोटा गलत हो जाएगा

[3] पारसेक का उपयोग करके एक हास्केल पार्सर का स्निपेट: एक चार फ़ंक्शन कैलक्यूलेटर एक्सपोनेंट्स, कोष्ठक, गुणा के लिए व्हाइटस्पेस, और स्थिरांक (जैसे पीआई और ई) के साथ बढ़ाया गया है।

aexpr   =   expr `chainl1` toOp
expr    =   optChainl1 term addop (toScalar 0)
term    =   factor `chainl1` mulop
factor  =   sexpr  `chainr1` powop
sexpr   =   parens aexpr
        <|> scalar
        <|> ident

powop   =   sym "^" >>= return . (B Pow)
        <|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))

toOp    =   sym "->" >>= return . (B To)

mulop   =   sym "*" >>= return . (B Mul)
        <|> sym "/" >>= return . (B Div)
        <|> sym "%" >>= return . (B Mod)
        <|>             return . (B Mul)

addop   =   sym "+" >>= return . (B Add) 
        <|> sym "-" >>= return . (B Sub)

scalar = number >>= return . toScalar

ident  = literal >>= return . Lit

parens p = do
             lparen
             result <- p
             rparen
             return result

मैंने एक साधारण स्टैक एल्गोरिदम का उपयोग करके एक समीकरण पार्सर विकसित किया है जो बाइनरी (+, -, |,,,, *, /, आदि) ऑपरेटरों, यूनरी (!) ऑपरेटरों और ब्रांडेसिस को संभालेगा।

इस विधि का उपयोग करते हुए, मुझे वही प्राथमिकता वाले सभी चीज़ों के साथ छोड़ देता है - ऑपरेटर के बावजूद इसे बाएं से दाएं मूल्यांकन किया जाता है, हालांकि प्राथमिकता कोष्ठक का उपयोग करके लागू किया जा सकता है।

तो अभी "1 + 11 * 5" 60 लौटाता है, 56 नहीं, जैसा कि कोई उम्मीद कर सकता है।

हालांकि यह वर्तमान परियोजना के लिए उपयुक्त है, लेकिन मैं एक सामान्य उद्देश्य नियमित करना चाहता हूं जिसका उपयोग मैं बाद की परियोजनाओं के लिए कर सकता हूं।

स्पष्टता के लिए संपादित:

प्राथमिकता के साथ समीकरणों को पार्स करने के लिए एक अच्छा एल्गोरिदम क्या है?

मुझे कुछ सरल बनाने में दिलचस्पी है और समझ में आता है कि मैं उपलब्ध कोड के साथ लाइसेंसिंग मुद्दों से बचने के लिए खुद को कोड कर सकता हूं।

व्याकरण:

मुझे व्याकरण प्रश्न नहीं समझा - मैंने इसे हाथ से लिखा है। यह इतना आसान है कि मुझे वाईएसीसी या बाइसन की आवश्यकता नहीं दिखाई दे रही है। मुझे केवल "2 + 3 * (42/13)" समीकरणों के साथ स्ट्रिंग की गणना करने की आवश्यकता है।

भाषा:

मैं इसे सी में कर रहा हूं, लेकिन मुझे एक एल्गोरिदम में रूचि है, न कि एक भाषा विशिष्ट समाधान। सी कम स्तर इतना है कि आवश्यकता होने पर दूसरी भाषा में परिवर्तित करना आसान हो जाएगा।

कोड उदाहरण

मैंने सरल अभिव्यक्ति पार्सर के लिए टेस्ट कोड पोस्ट किया था जो मैं ऊपर के बारे में बात कर रहा था। परियोजना की आवश्यकताओं में बदलाव आया और इसलिए मुझे प्रदर्शन या स्थान के लिए कोड को अनुकूलित करने की आवश्यकता नहीं थी क्योंकि इसे परियोजना में शामिल नहीं किया गया था। यह मूल verbose रूप में है, और आसानी से समझने योग्य होना चाहिए। यदि मैं ऑपरेटर प्राथमिकता के मामले में इसके साथ कुछ और करता हूं, तो शायद मैं मैक्रो हैक चुनूंगा क्योंकि यह सादगी में शेष कार्यक्रम से मेल खाता है। यदि मैंने कभी भी वास्तविक परियोजना में इसका उपयोग किया है, तो भी, मैं एक अधिक कॉम्पैक्ट / तेज़ पार्सर के लिए जा रहा हूं।

संबंधित सवाल

गणित पार्सर का स्मार्ट डिज़ाइन?

-Adam


इससे मदद मिलेगी यदि आप उस व्याकरण का वर्णन कर सकते हैं जिसका आप वर्तमान में पार्स का उपयोग कर रहे हैं। समस्या की तरह लगता है वहाँ झूठ बोल सकता है!

संपादित करें:

तथ्य यह है कि आप व्याकरण प्रश्न को नहीं समझते हैं और 'आपने इसे हाथ से लिखा है' बहुत ही संभावना बताती है कि आपको फॉर्म '1 + 11 * 5' (यानी ऑपरेटर प्राथमिकता के साथ) के अभिव्यक्तियों में समस्या क्यों आ रही है। । 'अंकगणितीय अभिव्यक्तियों के लिए व्याकरण' के लिए गुगलिंग, उदाहरण के लिए, कुछ अच्छे पॉइंटर्स पैदा करना चाहिए। इस तरह के व्याकरण को जटिल नहीं होना चाहिए:

<Exp> ::= <Exp> + <Term> |
          <Exp> - <Term> |
          <Term>

<Term> ::= <Term> * <Factor> |
           <Term> / <Factor> |
           <Factor>

<Factor> ::= x | y | ... |
             ( <Exp> ) |
             - <Factor> |
             <Number>

उदाहरण के लिए चाल करेगा, और कुछ और जटिल अभिव्यक्तियों (उदाहरण के लिए कार्यों, या शक्तियों सहित ...) की देखभाल करने के लिए इसे छोटा रूप से बढ़ाया जा सकता है।

मेरा सुझाव है कि उदाहरण के लिए, आपको this धागे पर एक नज़र डालें।

व्याकरण / पार्सिंग के लगभग सभी परिचय एक उदाहरण के रूप में अंकगणितीय अभिव्यक्ति का इलाज करते हैं।

ध्यान दें कि व्याकरण का उपयोग किसी विशिष्ट उपकरण ( ला लाक, बाइसन, ...) का उपयोग करके बिल्कुल नहीं है। दरअसल, आप निश्चित रूप से पहले से ही निम्नलिखित व्याकरण का उपयोग कर रहे हैं:

<Exp>  :: <Leaf> | <Exp> <Op> <Leaf>

<Op>   :: + | - | * | /

<Leaf> :: <Number> | (<Exp>)

(या किसी तरह का कुछ) इसे जानने के बिना!


क्या आपने बूस्ट स्पिरिट का उपयोग करने के बारे में सोचा है? यह आपको सी ++ में ईबीएनएफ-जैसे व्याकरण लिखने की अनुमति देता है:

group       = '(' >> expression >> ')';
factor      = integer | group;
term        = factor >> *(('*' >> factor) | ('/' >> factor));
expression  = term >> *(('+' >> term) | ('-' >> term));

क्या कोई ऐसी भाषा है जिसका आप उपयोग करना चाहते हैं? ANTLR आपको जावा परिप्रेक्ष्य से ऐसा करने देगा। रूबी में एक निष्पादन योग्य व्याकरण कैसे लिखना है, इस पर एड्रियन कुह्न का उत्कृष्ट writeup ; वास्तव में, उसका उदाहरण लगभग आपके अंकगणितीय अभिव्यक्ति उदाहरण है।


पाइपर्सिंग का उपयोग कर एक पाइथन समाधान here पाया जा सकता here । प्राथमिकता वाले विभिन्न ऑपरेटरों के साथ पार्सिंग इंफिक्स नोटेशन काफी आम है, और इसलिए पाइपर्सिंग में ऑपरेटर प्रेसीडेंस एक्सप्रेशन बिल्डर भी शामिल है। इसके साथ आप उदाहरण के लिए "AND", "OR", "NOT" का उपयोग करके बूलियन अभिव्यक्तियों को आसानी से परिभाषित कर सकते हैं। या आप अन्य ऑपरेटरों का उपयोग करने के लिए अपने चार-फ़ंक्शन अंकगणित का विस्तार कर सकते हैं, जैसे कि! मॉड्यूलस के लिए फैक्टोरियल, या '%' के लिए, या क्रमपरिवर्तन और संयोजनों की गणना करने के लिए पी और सी ऑपरेटरों को जोड़ें। आप मैट्रिक्स नोटेशन के लिए एक इंफिक्स पार्सर लिख सकते हैं, जिसमें '-1' या 'टी' ऑपरेटर (इनवर्जन और ट्रांसपोज़र के लिए) को संभालना शामिल है। एक 4-फ़ंक्शन पार्सर का ऑपरेटर प्रेसीडेंस उदाहरण (मज़े के लिए फेंक दिया गया '!' here और here एक अधिक पूर्ण रूप से प्रदर्शित पार्सर और मूल्यांकनकर्ता है।


प्राथमिकता पार्सिंग के लिए एक अन्य संसाधन विकिपीडिया पर ऑपरेटर-प्राथमिकता पार्सर प्रविष्टि है। डिजस्ट्रा के शंटिंग यार्ड एल्गोरिदम को कवर करता है, और एक पेड़ वैकल्पिक एल्गोरिदम, लेकिन अधिक उल्लेखनीय रूप से एक साधारण मैक्रो प्रतिस्थापन एल्गोरिदम को कवर करता है जिसे किसी भी प्राथमिकता अज्ञात पार्सर के सामने छोटे पैमाने पर कार्यान्वित किया जा सकता है:

#include <stdio.h>
int main(int argc, char *argv[]){
  printf("((((");
  for(int i=1;i!=argc;i++){
    if(argv[i] && !argv[i][1]){
      switch(argv[i]){
      case '^': printf(")^("); continue;
      case '*': printf("))*(("); continue;
      case '/': printf("))/(("); continue;
      case '+': printf(")))+((("); continue;
      case '-': printf(")))-((("); continue;
      }
    }
    printf("%s", argv[i]);
  }
  printf("))))\n");
  return 0;
}

इसे इस प्रकार शामिल करें:

$ cc -o parenthesise parenthesise.c
$ ./parenthesise a \* b + c ^ d / e
((((a))*((b)))+(((c)^(d))/((e))))

जो इसकी सादगी में बहुत ही बढ़िया है, और बहुत समझ में आता है।


मुझे पता है कि यह एक देर का जवाब है, लेकिन मैंने अभी एक छोटा पार्सर लिखा है जो सभी ऑपरेटरों (उपसर्ग, पोस्टफिक्स और इंफिक्स-बाएं, इंफिक्स-दाएं और गैर-संवादात्मक) को मनमाने ढंग से प्राथमिकता देता है।

मैं इसे मनमाने ढंग से डीएसएल समर्थन के साथ एक भाषा के लिए विस्तारित करने जा रहा हूं, लेकिन मैं सिर्फ यह इंगित करना चाहता था कि किसी को ऑपरेटर प्राथमिकता के लिए कस्टम पार्सर्स की आवश्यकता नहीं है, कोई एक सामान्यीकृत पार्सर का उपयोग कर सकता है जिसे टेबल की आवश्यकता नहीं होती है, और जैसा कि प्रतीत होता है, बस प्रत्येक ऑपरेटर की प्राथमिकता को देखता है। लोग कस्टम प्रैट पार्सर्स या शंटिंग यार्ड पार्सर्स का उल्लेख कर रहे हैं जो अवैध इनपुट स्वीकार कर सकते हैं - इसे अनुकूलित करने की आवश्यकता नहीं है और (जब तक कोई बग नहीं है) खराब इनपुट स्वीकार नहीं करेगा। यह एक अर्थ में पूर्ण नहीं है, यह एल्गोरिदम का परीक्षण करने के लिए लिखा गया था और इसका इनपुट एक ऐसे रूप में है जिसे कुछ प्रीप्रोकैसिंग की आवश्यकता होगी, लेकिन ऐसी टिप्पणियां हैं जो इसे स्पष्ट करती हैं।

नोट करें कि कुछ सामान्य प्रकार के ऑपरेटर अनुपलब्ध हैं उदाहरण के लिए ऑपरेटर के प्रकार का उपयोग इंडेक्सिंग यानी टेबल [इंडेक्स] या फ़ंक्शन फ़ंक्शन (पैरामीटर-अभिव्यक्ति, ...) को कॉल करने के लिए किया जाता है, मैं उनको जोड़ने जा रहा हूं, लेकिन पोस्टफिक्स के रूप में दोनों के बारे में सोचता हूं ऑपरेटर जहां डेलीमीटर '[' और ']' या '(' और ')' के बीच आता है, अभिव्यक्ति पार्सर के एक अलग उदाहरण के साथ पार्स किया जाता है। क्षमा करने के लिए खेद है, लेकिन पोस्टफिक्स भाग में है - शेष जोड़ना शायद कोड के आकार को लगभग दोगुना कर देगा।

चूंकि पार्सर रैकेट कोड की केवल 100 लाइनें हैं, शायद मुझे इसे यहां पेस्ट करना चाहिए, मुझे उम्मीद है कि यह स्टैक ओवरफ्लो अनुमतियों से अधिक नहीं है।

मनमाने ढंग से फैसले पर कुछ विवरण:

यदि कम प्राथमिकता पोस्टफिक्स ऑपरेटर एक समान इन्फिक्स ब्लॉक के लिए प्रतिस्पर्धा कर रहा है, तो निम्न प्राथमिकता उपसर्ग ऑपरेटर के रूप में उपसर्ग ऑपरेटर जीतता है। यह ज्यादातर भाषाओं में नहीं आता है क्योंकि अधिकांश में निम्न प्राथमिकता पोस्टफिक्स ऑपरेटर नहीं होते हैं। - उदाहरण के लिए: ((डेटा ए) (बाएं 1 +) (पूर्व 2 नहीं) (डेटा बी) (पोस्ट 3!) (बाएं 1 +) (डेटा सी)) एक + नहीं बी है! + सी जहां नहीं है उपसर्ग ऑपरेटर और! पोस्टफिक्स ऑपरेटर है और दोनों की तुलना में कम प्राथमिकता है + इसलिए वे इन मामलों में असंगत तरीकों से समूह (ए + नहीं बी!) + सी या एक + (बी नहीं! + सी) के रूप में समूह करना चाहते हैं, उपसर्ग ऑपरेटर हमेशा जीतता है, इसलिए दूसरी तरह यह पार्स है

गैर-संवादात्मक इंफिक्स ऑपरेटर वास्तव में वहां हैं ताकि आपको उन ऑपरेटरों का नाटक करने की आवश्यकता न हो जो विभिन्न प्रकारों को एक साथ समझने से पहले लौटते हैं, लेकिन प्रत्येक के लिए अलग-अलग अभिव्यक्ति प्रकारों के बिना यह एक क्लज है। इस प्रकार, इस एल्गोरिदम में, गैर-सहयोगी ऑपरेटरों न केवल अपने साथ बल्कि किसी भी ऑपरेटर के साथ समान प्राथमिकता के साथ जुड़ने से इनकार करते हैं। यह एक आम मामला है <<= ==> = आदि अधिकांश भाषाओं में एक दूसरे के साथ संबद्ध नहीं है।

सवाल यह है कि कैसे विभिन्न प्रकार के ऑपरेटरों (बाएं, उपसर्ग इत्यादि) प्राथमिकता पर संबंध तोड़ते हैं वह ऐसा नहीं होता है, क्योंकि यह वास्तव में अलग-अलग प्रकार के ऑपरेटरों को समान प्राथमिकता देने के लिए समझ में नहीं आता है। यह एल्गोरिदम उन मामलों में कुछ करता है, लेकिन मैं यह भी समझने के लिए परेशान नहीं हूं कि इस तरह के व्याकरण पहले स्थान पर एक बुरा विचार है।

#lang racket
;cool the algorithm fits in 100 lines!
(define MIN-PREC -10000)
;format (pre prec name) (left prec name) (right prec name) (nonassoc prec name) (post prec name) (data name) (grouped exp)
;for example "not a*-7+5 < b*b or c >= 4"
;which groups as: not ((((a*(-7))+5) < (b*b)) or (c >= 4))"
;is represented as '((pre 0 not)(data a)(left 4 *)(pre 5 -)(data 7)(left 3 +)(data 5)(nonassoc 2 <)(data b)(left 4 *)(data b)(right 1 or)(data c)(nonassoc 2 >=)(data 4)) 
;higher numbers are higher precedence
;"(a+b)*c" is represented as ((grouped (data a)(left 3 +)(data b))(left 4 *)(data c))

(struct prec-parse ([data-stack #:mutable #:auto]
                    [op-stack #:mutable #:auto])
  #:auto-value '())

(define (pop-data stacks)
  (let [(data (car (prec-parse-data-stack stacks)))]
    (set-prec-parse-data-stack! stacks (cdr (prec-parse-data-stack stacks)))
    data))

(define (pop-op stacks)
  (let [(op (car (prec-parse-op-stack stacks)))]
    (set-prec-parse-op-stack! stacks (cdr (prec-parse-op-stack stacks)))
    op))

(define (push-data! stacks data)
    (set-prec-parse-data-stack! stacks (cons data (prec-parse-data-stack stacks))))

(define (push-op! stacks op)
    (set-prec-parse-op-stack! stacks (cons op (prec-parse-op-stack stacks))))

(define (process-prec min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((>= (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-prec min-prec stacks))))))))

(define (process-nonassoc min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((> (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-nonassoc min-prec stacks))
                   ((= (cadr op) min-prec) (error "multiply applied non-associative operator"))
                   ))))))

(define (apply-op op stacks)
  (let [(op-type (car op))]
    (cond ((eq? op-type 'post)
           (push-data! stacks `(,op ,(pop-data stacks) )))
          (else ;assume infix
           (let [(tos (pop-data stacks))]
             (push-data! stacks `(,op ,(pop-data stacks) ,tos))))))) 

(define (finish input min-prec stacks)
  (process-prec min-prec stacks)
  input
  )

(define (post input min-prec stacks)
  (if (null? input) (finish input min-prec stacks)
      (let* [(cur (car input))
             (input-type (car cur))]
        (cond ((eq? input-type 'post)
               (cond ((< (cadr cur) min-prec)
                      (finish input min-prec stacks))
                     (else 
                      (process-prec (cadr cur)stacks)
                      (push-data! stacks (cons cur (list (pop-data stacks))))
                      (post (cdr input) min-prec stacks))))
              (else (let [(handle-infix (lambda (proc-fn inc)
                                          (cond ((< (cadr cur) min-prec)
                                                 (finish input min-prec stacks))
                                                (else 
                                                 (proc-fn (+ inc (cadr cur)) stacks)
                                                 (push-op! stacks cur)
                                                 (start (cdr input) min-prec stacks)))))]
                      (cond ((eq? input-type 'left) (handle-infix process-prec 0))
                            ((eq? input-type 'right) (handle-infix process-prec 1))
                            ((eq? input-type 'nonassoc) (handle-infix process-nonassoc 0))
                            (else error "post op, infix op or end of expression expected here"))))))))

;alters the stacks and returns the input
(define (start input min-prec stacks)
  (if (null? input) (error "expression expected")
      (let* [(cur (car input))
             (input-type (car cur))]
        (set! input (cdr input))
        ;pre could clearly work with new stacks, but could it reuse the current one?
        (cond ((eq? input-type 'pre)
               (let [(new-stack (prec-parse))]
                 (set! input (start input (cadr cur) new-stack))
                 (push-data! stacks 
                             (cons cur (list (pop-data new-stack))))
                 ;we might want to assert here that the cdr of the new stack is null
                 (post input min-prec stacks)))
              ((eq? input-type 'data)
               (push-data! stacks cur)
               (post input min-prec stacks))
              ((eq? input-type 'grouped)
               (let [(new-stack (prec-parse))]
                 (start (cdr cur) MIN-PREC new-stack)
                 (push-data! stacks (pop-data new-stack)))
               ;we might want to assert here that the cdr of the new stack is null
               (post input min-prec stacks))
              (else (error "bad input"))))))

(define (op-parse input)
  (let [(stacks (prec-parse))]
    (start input MIN-PREC stacks)
    (pop-data stacks)))

(define (main)
  (op-parse (read)))

(main)

मैं धोखा देने और शंटिंग यार्ड एल्गोरिदम का उपयोग करने का सुझाव दूंगा । यह एक साधारण कैलकुलेटर-प्रकार पार्सर लिखने का एक आसान माध्यम है और खाते में प्राथमिकता लेता है।

यदि आप चीजों को सही ढंग से टोकन करना चाहते हैं और इसमें चर, इत्यादि शामिल हैं तो मैं आगे बढ़ूंगा और दूसरों द्वारा सुझाए गए एक रिकर्सिव वंश पार्सर लिखूंगा, हालांकि यदि आपको केवल कैलकुलेटर-शैली पार्सर की आवश्यकता होती है तो यह एल्गोरिदम पर्याप्त होना चाहिए :-)




मैंने इसे शंटिंग यार्ड एल्गोरिदम के बारे में PIClist पर पाया:

हैरोल्ड लिखते हैं:

मुझे एक एल्गोरिदम का बहुत समय पहले पढ़ना याद है, जो कि आसान मूल्यांकन के लिए बीजगणित अभिव्यक्तियों को आरपीएन में परिवर्तित करता है। एक ट्रैक पर एक रेलरोड कार द्वारा प्रत्येक इंफिक्स मूल्य या ऑपरेटर या कोष्ठक का प्रतिनिधित्व किया गया था। एक प्रकार की कार दूसरे ट्रैक पर विभाजित होती है और दूसरी सीधी आगे बढ़ती है। मुझे विवरण याद नहीं है (जाहिर है!), लेकिन हमेशा सोचा कि यह कोड के लिए दिलचस्प होगा। यह वापस आ गया है जब मैं 6800 (68000 नहीं) असेंबली कोड लिख रहा था।

यह "शंटिंग यार्ड एल्गोरिथ्म" है और यह अधिकांश मशीन पार्सर्स का उपयोग करता है। विकिपीडिया में पार्सिंग पर आलेख देखें। शंटिंग यार्ड algorythm कोड करने का एक आसान तरीका दो ढेर का उपयोग करना है। एक "धक्का" ढेर है और दूसरा "कम" या "परिणाम" ढेर है। उदाहरण:

pstack = () // खाली rstack = () इनपुट: 1 + 2 * 3 प्राथमिकता = 10 // सबसे कम कम = 0 // कम नहीं

शुरू करें: टोकन '1': संख्या, पस्टैक (पुश) टोकन '+' में डाल दिया गया है: isoperator set precedence = 2 अगर प्राथमिकता <पिछला_ऑपरेटर_प्रसेडेंस फिर कम करें () // नीचे देखें '+' को pstack (push) टोकन '2' में रखें : isnumber, pstack (पुश) टोकन '*' में डाल दिया: isoperator, सेट precedence = 1, pstack (पुश) में डाल // // उपरोक्त टोकन '3' के रूप में पूर्व की जांच करें: isnumber, pstack (धक्का) अंत में डाल दिया इनपुट, कम करने की आवश्यकता है (लक्ष्य खाली पस्टैक है) कम करें () // किया गया

पुश स्टैक से पॉप तत्वों को कम करने के लिए, उन्हें परिणाम स्टैक में डाल दें, अगर वे 'ऑपरेटर' 'फॉर्म' के रूप में हैं तो हमेशा शीर्ष 2 आइटम को पस्टैक पर स्वैप करें:

pstack: '1' '+' '2' '' '' 3 'rstack: () ... pstack: () rstack:' 3 '' 2 '' '' 1 '' + '

अगर अभिव्यक्ति होती:

1 * 2 + 3

तो कम ट्रिगर टोकन '+' का पठन होता जो कि पहले से धक्का '*' से कम सटीक होता है, इसलिए यह होता:

pstack: '1' ' ' '2' rstack: () ... pstack: () rstack: '1' '2' ' '

और फिर '+' और फिर '3' को धक्का दिया और फिर अंत में कम हो गया:

pstack: '+' '3' rstack: '1' '2' ' ' ... pstack: () rstack: '1' '2' ' ' '3' '+'

तो लघु संस्करण है: पुश संख्या, ऑपरेटरों को धक्का देते समय पिछले ऑपरेटर की प्राथमिकता की जांच करें। यदि यह ऑपरेटर की तुलना में अधिक था जिसे अब धक्का दिया जाना है, तो पहले कम करें, फिर वर्तमान ऑपरेटर को दबाएं। माता-पिता को संभालने के लिए बस 'पिछले' ऑपरेटर की प्राथमिकता को बचाएं, और पैस्टैक पर एक निशान डालें जो अल्जीरीथम को कम करता है ताकि माता-पिता की जोड़ी के अंदर को हल करते समय कम किया जा सके। समापन माता-पिता इनपुट के अंत के रूप में कमी को ट्रिगर करता है, और पस्टैक से खुले पैरों के निशान को भी हटा देता है, और 'पिछले ऑपरेशन' की प्राथमिकता को पुनर्स्थापित करता है, इसलिए बंद पैर के बाद पार्सिंग जारी रह सकती है। यह रिकर्सन के बिना या बिना किया जा सकता है (संकेत: '(' ...) का सामना करते समय पिछली प्राथमिकता को स्टोर करने के लिए एक स्टैक का उपयोग करें। इसका सामान्य संस्करण एक पार्सर जेनरेटर को लागू करने के लिए लागू किया गया है यार्ड algorythm, f.ex. yacc या bison या taccle (yacc का tcl एनालॉग) का उपयोग करना।

पीटर

-Adam


मैंने एफ # में एक अभिव्यक्ति पार्सर लिखा और इसके बारे में यहां ब्लॉग किया । यह शंटिंग यार्ड एल्गोरिदम का उपयोग करता है, लेकिन इन्फिक्स से RPN में कनवर्ट करने के बजाय, मैंने गणना के परिणामों को जमा करने के लिए एक दूसरा स्टैक जोड़ा। यह ऑपरेटर प्राथमिकता को सही ढंग से संभालता है, लेकिन यूनरी ऑपरेटरों का समर्थन नहीं करता है। मैंने एफ # सीखने के लिए यह लिखा, अभिव्यक्ति पार्सिंग सीखने के लिए नहीं, हालांकि।


शंटिंग यार्ड एल्गोरिदम इस के लिए सही उपकरण है। विकिपीडिया वास्तव में इस बारे में भ्रमित है, लेकिन मूल रूप से एल्गोरिदम इस तरह काम करता है:

कहें, आप 1 + 2 * 3 + 4 का मूल्यांकन करना चाहते हैं। सहजता से, आप "जानते हैं" आपको पहले 2 * 3 करना है, लेकिन आप यह परिणाम कैसे प्राप्त करते हैं? कुंजी यह जानना है कि जब आप स्ट्रिंग को बाएं से दाएं स्कैन कर रहे हैं, तो आप ऑपरेटर का मूल्यांकन करेंगे जब उसके बाद ऑपरेटर कम (या बराबर) प्राथमिकता रखता है। उदाहरण के संदर्भ में, आप यहां क्या करना चाहते हैं:

  1. देखो: 1 + 2, कुछ भी मत करो।
  2. अब 1 + 2 * 3 देखें, अभी भी कुछ भी मत करो।
  3. अब 1 + 2 * 3 + 4 देखें, अब आप जानते हैं कि 2 * 3 का मूल्यांकन किया जाना चाहिए क्योंकि अगले ऑपरेटर की प्राथमिकता कम है।

आप इसे कैसे कार्यान्वित करते हैं?

आप दो ढेर, संख्याओं के लिए एक और ऑपरेटरों के लिए एक और चाहते हैं। आप हर समय ढेर पर संख्याओं को धक्का देते हैं। आप प्रत्येक नए ऑपरेटर की तुलना स्टैक के शीर्ष पर एक के साथ करते हैं, यदि स्टैक के शीर्ष पर एक व्यक्ति को प्राथमिकता दी जाती है, तो आप इसे ऑपरेटर स्टैक से पॉप करते हैं, ऑपरेटर को नंबर स्टैक से पॉप करते हैं, ऑपरेटर को लागू करते हैं और परिणाम दबाते हैं संख्या ढेर पर। अब आप स्टैक ऑपरेटर के शीर्ष के साथ तुलना दोहराते हैं।

उदाहरण के लिए वापस आ रहा है, यह इस तरह काम करता है:

एन = [] ओपीएस = []

  • पढ़ें 1. एन = [1], ओपीएस = []
  • पढ़ें +। एन = [1], ओपीएस = [+]
  • पढ़ें 2. एन = [1 2], ओपीएस = [+]
  • पढ़ें * । एन = [1 2], ओपीएस = [+ *]
  • पढ़ें 3. एन = [1 2 3], ओपीएस = [+ *]
  • पढ़ें +। एन = [1 2 3], ओपीएस = [+ *]
    • पॉप 3, 2 और 2 * 3 निष्पादित करें, और परिणाम एन एन = [1 6], ओपीएस = [+] पर दबाएं
    • + को सहयोगी छोड़ दिया गया है, इसलिए आप 1, 6 को भी पॉप करना चाहते हैं और + निष्पादित करना चाहते हैं। एन = [7], ओपीएस = []।
    • अंत में ऑपरेटर स्टैक पर [+] दबाएं। एन = [7], ओपीएस = [+]।
  • पढ़ें 4. एन = [7 4]। ओपीएस = [+]।
  • आप इनपुट बंद कर चुके हैं, इसलिए आप अब ढेर खाली करना चाहते हैं। जिस पर आपको परिणाम 11 मिलेगा।

वहां, यह इतना मुश्किल नहीं है, है ना? और यह किसी भी व्याकरण या पार्सर जेनरेटर को कोई आविष्कार नहीं करता है।


http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm

विभिन्न दृष्टिकोणों की बहुत अच्छी व्याख्या:

  • रिकर्सिव-वंश पहचान
  • शंटिंग यार्ड एल्गोरिदम
  • क्लासिक समाधान
  • प्राथमिकता चढ़ाई

सरल भाषा और छद्म कोड में लिखा है।

मुझे 'प्राथमिकता चढ़ाई' पसंद है।


Here is a simple case recursive solution written in Java. Note it does not handle negative numbers but you can do add that if you want to:

public class ExpressionParser {

public double eval(String exp){
    int bracketCounter = 0;
    int operatorIndex = -1;

    for(int i=0; i<exp.length(); i++){
        char c = exp.charAt(i);
        if(c == '(') bracketCounter++;
        else if(c == ')') bracketCounter--;
        else if((c == '+' || c == '-') && bracketCounter == 0){
            operatorIndex = i;
            break;
        }
        else if((c == '*' || c == '/') && bracketCounter == 0 && operatorIndex < 0){
            operatorIndex = i;
        }
    }
    if(operatorIndex < 0){
        exp = exp.trim();
        if(exp.charAt(0) == '(' && exp.charAt(exp.length()-1) == ')')
            return eval(exp.substring(1, exp.length()-1));
        else
            return Double.parseDouble(exp);
    }
    else{
        switch(exp.charAt(operatorIndex)){
            case '+':
                return eval(exp.substring(0, operatorIndex)) + eval(exp.substring(operatorIndex+1));
            case '-':
                return eval(exp.substring(0, operatorIndex)) - eval(exp.substring(operatorIndex+1));
            case '*':
                return eval(exp.substring(0, operatorIndex)) * eval(exp.substring(operatorIndex+1));
            case '/':
                return eval(exp.substring(0, operatorIndex)) / eval(exp.substring(operatorIndex+1));
        }
    }
    return 0;
}

}







equation