জাভাস্ক্রিপ্ট (বা অন্য কোনও ভাষা) ব্যবহার করে গাণিতিক এক্সপ্রেশন টোকেনাইজার কীভাবে তৈরি করবেন

সূত্র: উইকিমিডিয়া কমন্স

কিছু সময় আগে, আমি নির্দিষ্ট ধরণের গণিত সমস্যা সমাধানের জন্য একটি অ্যাপ্লিকেশন বিকাশ করতে অনুপ্রাণিত হয়েছিলাম। আমি আবিষ্কার করেছি যে আমাকে প্রকাশটি একটি বিমূর্ত সিনট্যাক্স ট্রিতে রূপান্তর করতে হয়েছিল এবং জাভাস্ক্রিপ্টে এটি প্রোটোটাইপ করার সিদ্ধান্ত নিয়েছে। পার্সারে কাজ করার সময় আমি বুঝতে পেরেছিলাম যে প্রথমে টোকেনাইজার তৈরি করতে হবে। আমি আপনাকে এটি কীভাবে করবেন তা আপনাকে দেখাব। (সতর্কতা: এটি প্রথম দেখার চেয়ে সহজ)

টোকনাইজার কী?

টোকেনাইজার এমন একটি প্রোগ্রাম যা অভিব্যক্তিকে টোকেন নামে এককে বিভক্ত করে। উদাহরণস্বরূপ, যদি আমাদের "আমি একটি বড় ফ্যাট বিকাশকারী" এর মত প্রকাশ পাই তবে আমরা এটিকে বিভিন্ন উপায়ে লেবেল দিতে পারি যেমন:

টোকেন হিসাবে শব্দ ব্যবহার করুন,

0 => আমি 1 => এ 2 => লম্বা 3 => সাহসী 4 => বিকাশকারী

স্থান ছাড়াই টোকেন হিসাবে ব্যবহার করা,

0 => আমি 1 => 2 => মি 3 => এ 4 => বি… 16 => পি 17 => ই 18 => আর

আমরা সমস্ত অক্ষরগুলি পেতে টোকেন হিসাবে বিবেচনা করতে পারে

0 => আমি 1 => 2 => মি 3 => (স্পেস) 4 => এ 5 => (স্পেস) 6 => খ… 20 => পি 21 => ই 22 => আর

আপনার ধারণা আছে, তাই না?

টোকেনাইজার (যাকে লেক্সারও বলা হয়) প্রোগ্রামিং ভাষার জন্য সংকলকগুলির বিকাশে ব্যবহৃত হয়। আপনি কী বলতে চান তা তারা কাঠামোগতভাবে বুঝতে সংকলকটিকে সহায়তা করে। এই ক্ষেত্রে, তবে আমরা গাণিতিক প্রকাশের জন্য একটি তৈরি করি।

টোকেন

একটি বৈধ গাণিতিক প্রকাশের মধ্যে গাণিতিকভাবে বৈধ টোকেন থাকে, যা এই প্রকল্পের উদ্দেশ্যগুলির জন্য ফাংশন আর্গুমেন্টের জন্য আক্ষরিক, পরিবর্তনশীল, অপারেটর, ফাংশন বা বিভাজক হতে পারে। কয়েকটি মন্তব্য:

  • আক্ষরিক একটি সংখ্যার অভিনব নাম (এই ক্ষেত্রে)। আমরা কেবল পুরো বা দশমিক সংখ্যার অনুমতি দিই।
  • ভ্যারিয়েবল হ'ল আপনি গণিতে যে ধরণের ব্যবহার করতে চান: a, b, c, x, y, z। এই প্রকল্পের জন্য, সমস্ত ভেরিয়েবলগুলি একটি বর্ণের নামের সাথে সীমাবদ্ধ (ভার 1 বা দামের মতো কিছুই নয়)। এইভাবে আমরা মা এর মত একটি এক্সপ্রেশনটি ভ্যারিয়েবলের m এবং a এর পণ্য হিসাবে চিহ্নিত করতে পারি এবং একক ভেরিয়েবল মা হিসাবে চিহ্নিত করতে পারি না।
  • অপারেটরগুলি আক্ষরিক এবং পরিবর্তনশীল পাশাপাশি ফাংশনগুলির ফলাফল সম্পাদনা করে। আমরা অপারেটরদের +, -, *, / এবং ^ অনুমতি দিই ^
  • ফাংশনগুলি "আরও উন্নত" ক্রিয়াকলাপ। এর মধ্যে পাপ (), কোস (), ট্যান (), নূন্যতম (), সর্বাধিক () ইত্যাদি বিষয় অন্তর্ভুক্ত রয়েছে
  • একটি ফাংশন আর্গুমেন্ট বিভাজক একটি কমা জন্য শুধুমাত্র অভিনব নাম যা এই জাতীয় প্রসঙ্গে ব্যবহৃত হয়: সর্বাধিক (4, 5) (দুটি মানের সর্বোচ্চ)। আমরা এটিকে একটি ফাংশন আর্গুমেন্ট বিভাজক বলি কারণ এটি ফাংশন আর্গুমেন্টকে পৃথক করে (ফাংশনগুলির ক্ষেত্রে দুটি বা আরও বেশি আর্গুমেন্ট রয়েছে যেমন সর্বোচ্চ এবং সর্বনিম্ন)।

আমরা দুটি টোকেন যুক্ত করব যা সাধারণত টোকেন হিসাবে বিবেচিত হয় না, তবে এটি আমাদের পরিষ্কার করে তোলে: বাম এবং ডান বন্ধনী। আপনি কি জানেন তারা কি।

কিছু বিবেচনা

অন্তর্নির্মিত গুণ

অন্তর্নির্মিত গুণটির অর্থ কেবল ব্যবহারকারীকে "সংক্ষিপ্ত গুণ" লিখতে দেওয়া হয়, উদাহরণস্বরূপ 5 * x এর পরিবর্তে 5x। আপনি যদি আরও একধাপ এগিয়ে যান তবে আপনি এটি ফাংশন (5 সিন (এক্স) = 5 * পাপ (এক্স)) দিয়েও করতে পারেন।

এটি 5 (এক্স) এবং 5 (পাপ (এক্স)) এরও অনুমতি দেয়। আমাদের অনুমতি দেয় বা না করার বিকল্প রয়েছে। আপোস? আপনি যদি এটি অনুমতি না দেন তবে টোকেনিং সহজ হবে এবং বহু-বর্ণের পরিবর্তনশীল নাম (দামের মতো নাম) ব্যবহার করা যেতে পারে। আপনি যদি এটির অনুমতি দেন তবে প্ল্যাটফর্মটি ব্যবহারকারীর জন্য আরও স্বজ্ঞাত হয়ে ওঠে এবং একটি অতিরিক্ত চ্যালেঞ্জের প্রতিনিধিত্ব করে যা অতিক্রম করতে হবে। আমি এটি অনুমতি দিয়েছি।

শব্দবিন্যাস

আমরা যখন কোনও প্রোগ্রামিং ভাষা তৈরি করছি না, তখন কী বৈধ ভাব প্রকাশ করে তা সম্পর্কে আমাদের কিছু বিধি থাকা দরকার যাতে ব্যবহারকারীরা কী টাইপ করবেন এবং কী পরিকল্পনা করবেন তা জানুন। কঠোরভাবে বলতে গেলে, গাণিতিক টোকেনগুলি বাক্যটি বৈধ হওয়ার জন্য এই সিনট্যাক্স নিয়ম অনুসারে অবশ্যই একত্রিত করতে হবে। আমার নিয়মগুলি এখানে:

  1. টোকেনগুলি 0 বা আরও বেশি স্পেস দ্বারা পৃথক করা যায়
2 + 3, 2 + 3, 2 + 3, 2 + 3 সব ঠিক 5 x - 22, 5x-22, 5x-22 সব ঠিক আছে

অন্য কথায়, ব্যবধানটি কোনও ব্যাপার নয় (আক্ষরিক 22 এর মতো বহু-অঙ্কের টোকেন বাদে)।

2. ফাংশন আর্গুমেন্ট অবশ্যই প্রথম বন্ধনী (পাপ (y), কোস (45), পাপ নয়, কোস 45) এ থাকতে হবে। (কেন? আমরা স্ট্রিং থেকে সমস্ত ফাঁকা স্থান সরিয়ে ফেলি, তাই আমরা জানতে চাই যে কোনও ফাংশন শুরু হয় এবং কিছুটা জিমন্যাস্টিক না করেই শেষ হয়))

৩. এই ক্রমে কেবলমাত্র আক্ষরিক এবং পরিবর্তনশীল বা আক্ষরিক এবং ফাংশনগুলির মধ্যে অন্তর্নিহিত গুণগুলি অনুমোদিত and এবং বন্ধনীগুলির সাথে বা ছাড়াই নির্দিষ্ট করা যেতে পারে। এর অর্থ:

  • a (4) কে ফাংশন কল হিসাবে ধরা হয়, * 4 হিসাবে নয়
  • a4 অনুমোদিত নয়
  • 4 এ এবং 4 (ক) ঠিক আছে

এখন এটি কাজ করতে যায়।

তথ্য মডেলিং

এটি পরীক্ষা করার জন্য আপনার মাথায় একটি নমুনা প্রকাশ করা সহায়ক। আমরা প্রাথমিক কিছু: 2Y + 1 দিয়ে শুরু করি

আমরা যা প্রত্যাশা করি তা হ'ল একটি অ্যারে যা তাদের ধরণ এবং মানগুলির সাথে অভিব্যক্তিটির বিভিন্ন টোকেনকে তালিকাবদ্ধ করে। এই ক্ষেত্রে, আমরা আশা করি:

0 => আক্ষরিক (2) 1 => পরিবর্তনশীল (y) 2 => অপারেটর (+) 3 => আক্ষরিক (1)

প্রথমত, আমরা জিনিসগুলি সহজ করার জন্য একটি টোকেন শ্রেণি সংজ্ঞায়িত করি:

ফাংশন টোকেন (প্রকার, মান)। This.type = প্রকার; this.value = মান}

অ্যালগরিদম

এরপরে, আমরা আমাদের টোকেনাইজার ফাংশনের মেরুদণ্ডটি তৈরি করব।

আমাদের টোকেনাইজার স্ট্র অ্যারেতে প্রতিটি অক্ষর অনুসন্ধান করে এবং প্রাপ্ত মানটির ভিত্তিতে টোকেন তৈরি করে।

[মনে রাখবেন যে আমরা ধরে নিয়েছি যে ব্যবহারকারী আমাদের একটি বৈধ এক্সপ্রেশন দেবে, তাই আমরা এই প্রকল্পে কোনও বৈধতা যাচাই করব]]

ফাংশন টোকেনাইজ (স্ট্র) {var ফলাফল = []; // টোকেনের অ্যারে // স্পেসগুলি সরান; আপনি কি মনে করেন যে তারা কিছু যায় আসে না? str.replace (/ \ s + / g, "");
// অক্ষরের একটি অ্যারে রূপান্তর করুন str = str.split ("");
str.forEach (ফাংশন (চর, idx) {যদি (isDigit (চর)) {ফলাফল.push (নতুন টোকেন ("লিটারাল", চর));} অন্যথায় যদি (isLetter (চর)) {ফলাফল.push (নতুন টোকেন ("ভেরিয়েবল", চর));} অন্যথায় যদি (isOperator (চর)) {ফলাফল.push (নতুন টোকেন ("অপারেটর", চর));} অন্যথায় যদি (হয় লেফটপ্যারেন্টেসিস (চর)) {ফলাফল.push (নতুন টোকেন ("বাম পেরেন্টেসিস", চর));} অন্যথায় যদি (#RightParenthesis (চর)) {ফলাফল.push (নতুন টোকেন ("ডান বন্ধনী", চর)); অন্যথায় যদি (isComma (চর)) {ফলাফল.push ( নতুন টোকেন ("ফাংশন আর্গুমেন্ট সেপারেটর", চর));}});
রিটার্ন ফলাফল; }

উপরের কোডটি বেশ সহজ। রেফারেন্সের উদ্দেশ্যে, আইডিজিট (), আইস লেটার (), আইসপ্রেটার (), আইফেলপ্যারেন্টেসিস () এবং আইরাইটপ্যারেন্টেসিস () ইউটিলিটিগুলি নিম্নলিখিত হিসাবে সংজ্ঞায়িত করা হয়েছে:

ফাংশনটি কমমা (সিএইচ) {রিটার্ন (সিএইচ === ","); }
ফাংশনটি ডিজিট (সিএইচ) {রিটার্ন /\d/.test(ch); }
ফাংশনটি লেটার (সিএইচ) {রিটার্ন / শেয়াদেলিজেল / আই.টেসটেক; }
ফাংশনটি অপারেটর (সিএইচ) {রিটার্ন /\+/0-)\)\)ype\/ype\^/.test(ch); }
ফাংশন হ'ল লেফটপ্যারেন্টেসিস (সিএইচ) {রিটার্ন (সিএইচ === "(");}
ফাংশনটি হ'ল রাইটপ্যারেন্টেসিস (সিএইচ) {রিটার্ন (সিএইচ == ")"); }

[দ্রষ্টব্য যে এখানে কোনও ফাংশন (), isLiteral (), বা isAvariable () ফাংশন নেই কারণ আমরা একে একে অক্ষর পরীক্ষা করে থাকি।]

সুতরাং এখন আমাদের পার্সার আসলে কাজ করে। এই এক্সপ্রেশনগুলি চেষ্টা করুন: 2 + 3, 4 এ + 1, 5 এক্স + (2 ই), 11 + পাপ (20.4)।

তুমি ঠিক আছ?

ঠিক না।

আপনি লক্ষ্য করবেন যে শেষ প্রকাশের জন্য, 11 টির পরিবর্তে দুটি আক্ষরিক টোকেন হিসাবে প্রতিবেদন করা হয়েছে। একজনের পরিবর্তে তিনটি চরিত্র হিসাবেও পাপ হিসাবে রিপোর্ট করা হয়। কেন এমন?

আসুন আমরা এক মুহুর্তের জন্য থমকে থাকি এবং এটি সম্পর্কে চিন্তা করি। আমরা চরিত্র অনুসারে অ্যারে চরিত্রটিকে টোকন করেছি, তবে বাস্তবে আমাদের কিছু টোকনে একাধিক অক্ষর থাকতে পারে। লিটারালগুলি উদাহরণস্বরূপ 5, 7.9, 0.5 হতে পারে। ক্রিয়াকলাপ পাপ, কোস ইত্যাদি হতে পারে চলকগুলি কেবলমাত্র একক অক্ষর, তবে অন্তর্নিহিত গুণে একসাথে ঘটতে পারে। আমরা কীভাবে এটি সমাধান করব?

বাফার

আমরা একটি বাফার প্রয়োগ করে এটি ঠিক করতে পারি। আসলে দুটি। আমরা আক্ষরিক অক্ষরগুলির জন্য একটি বাফার ব্যবহার করি (সংখ্যা এবং দশমিক স্থান) এবং একটি অক্ষরের জন্য (যা উভয় ভেরিয়েবল এবং ফাংশনগুলি কভার করে)।

বাফাররা কীভাবে কাজ করবে? টোকেনাইজার যদি কোনও সংখ্যা / দশমিক বিন্দু বা কোনও চিঠির মুখোমুখি হয় তবে এটি এটি সম্পর্কিত বাফারে ঠেলে দেয় এবং অন্য অপারেটরে প্রবেশ না করা অবধি এটি চালিয়ে যেতে থাকে। অপারেটরের উপর নির্ভর করে ক্রিয়াগুলি পরিবর্তিত হয়।

উদাহরণস্বরূপ, 456.7xy + 6sin (7.04x) - মিনিট (ক, 7) এর অভিব্যক্তিতে এটি দেখতে দেখতে এটি দেখতে পারা উচিত:

4 => নম্বর বুফার পড়ুন 5 => নম্বর বুফার পড়ুন 6 => নম্বর বুফার পড়ুন। => নাম্বার বাফার পড়ুন = = সংখ্যা নম্বর বাফার এক্স একটি চিঠি, সুতরাং সংখ্যা বাফারের সম্পূর্ণ সামগ্রিকে আক্ষরিক হিসাবে একত্রিত করুন ৪66. = => ফলাফল পঠিত x => লেটারবুফার রিড y => লেটারফুফার + অপারেটর। অতএব, অক্ষর বাফারের সম্পূর্ণ বিষয়বস্তু পৃথকভাবে ভেরিয়েবল হিসাবে মুছে ফেলুন x => ফলাফল, y => ফলাফল + => ফলাফল পড়ুন = => সংখ্যাবাফার গুলি একটি চিঠি, সুতরাং সংখ্যা বাফারের পুরো বিষয়বস্তুকে আক্ষরিক 6 => ফলাফল হিসাবে যুক্ত করুন => লেটারফুফার i => লেটারফুফার পড়েন এন => লেটারফার (একটি বাম বন্ধনী, সুতরাং একটি ফাংশন হিসাবে লেটার বাফারের পুরো বিষয়বস্তু একসাথে রাখুন => ফলাফল পড়ুন = => পঠন নম্বর বুফার। => সংখ্যা বাফার পড়ুন = = সংখ্যা সংখ্যা বাফার ৪) => সংখ্যাবাফার এক্স একটি চিঠি, সুতরাং সংখ্যা বাফারের সম্পূর্ণ বিষয়বস্তুকে আক্ষরিক হিসাবে ine.০৪ => ফলাফল পড়ুন x => লেটারফুফার হিসাবে একত্রিত করুন) একটি সঠিক বন্ধনী। অতএব, পরিবর্তনশীল x => এর ফলস্বরূপ লেটার বাফারের সম্পূর্ণ সামগ্রী আলাদাভাবে সরান - একটি অপারেটর, তবে উভয় বাফার খালি রয়েছে, তাই অপসারণ করার মতো কিছুই নেই Read এম => লেটারবার্ফার পড়ুন i => লেটারবুফার পড়ুন n => লেটারফুফার (এটি একটি বাম বন্ধনী, সুতরাং একটি ফাংশন হিসাবে লেটার বাফারের পুরো সামগ্রীতে যোগ করুন মিনিট => ফলাফল পড়ুন a => লেটারফুফারটি একটি কমা, সুতরাং লেটারবফারের সম্পূর্ণ সামগ্রীতে একটি চলক a => ফলাফল হিসাবে যোগদান করুন এবং তারপরে একটি ফাংশন হিসাবে চাপ দিন => ফলাফল পড়ুন = => নাম্বার বাফার) হ'ল একটি প্রথম বন্ধনী, সুতরাং সংখ্যা বাফারের সম্পূর্ণ বিষয়বস্তুকে আক্ষরিক = => ফলাফল হিসাবে একত্রিত করুন

সম্পূর্ণরূপে। আপনি এখন এটি হ্যাঙ পেয়েছেন, তাই না?

আমরা কেবল কয়েকটি মামলা প্রক্রিয়া করার জন্য আছে।

এটি সেই জায়গা যেখানে আপনি বসে আপনার অ্যালগরিদম এবং আপনার ডেটা মডেলিং সম্পর্কে গভীরভাবে চিন্তা করেন। আমার বর্তমান চরিত্রটি যদি অপারেটর হয় এবং নম্বর বুফারটি খালি না থাকে তবে কী ঘটবে? উভয় বাফার কি একই সময়ে খালি থাকতে পারে না?

আসুন সমস্ত সংক্ষিপ্ত বিবরণ দিন: তীরটির বামে থাকা মানগুলি আমাদের বর্তমান অক্ষরের ধরণ (সিএইচ) নির্দেশ করে। এনবি = নম্বর বাফার, এলবি = লেটার বাফার, এলপি = বাম বন্ধনী, আরপি = ডান বন্ধনী

অ্যারের মাধ্যমে লুপ: ch কি ধরণের?
ডিজিট => এনবি দশমিক বিন্দুতে চিপ টিপুন>> সিবি টিপুন এনবি লেটার => আক্ষরিক হিসাবে NB সামগ্রীগুলিকে মার্জ করুন এবং ফলাফলের জন্য টিপুন, তারপরে সিবি টিপুন LB অপারেটরকে>> আক্ষরিক হিসাবে NB সামগ্রীগুলিকে মার্জ করুন এবং টিপুন ফলাফলের জন্য বা পৃথকভাবে ভেরিয়েবল হিসাবে এলবি বিষয়বস্তু টিপুন এবং তারপরে ফল টিপুন চিপ টিপুন এলপি => একটি ফাংশন হিসাবে এলবি বিষয়বস্তু একত্রিত করুন এবং ফলাফলটিতে টিপুন বা (আক্ষরিক হিসাবে এনবি বিষয়বস্তু একত্রিত করুন এবং ফলাফলটিতে টিপুন, ফলাফলটিতে অপারেটর টিপুন) , তারপরে আরপি => এনবি বিষয়বস্তুকে আক্ষরিক হিসাবে মার্জ করতে ch টিপুন এবং ফলস্বরূপ টিপুন, এলবি বিষয়বস্তুকে পৃথকভাবে ভেরিয়েবল হিসাবে টিপুন এবং তারপরে ch টিপুন ফলস্বরূপ কমা => এনবি কনটেন্টগুলিকে আক্ষরিক হিসাবে মার্জ করুন এবং ফলাফলটি পেতে টিপুন, ভেরিয়েবল হিসাবে পৃথকভাবে LB সামগ্রী টিপুন এবং তারপরে ফলাফল পেতে ch টিপুন
শেষ লুপ
আক্ষরিক হিসাবে এনবি সামগ্রী একত্রিত করুন এবং ফলাফল টিপুন। ভেরিয়েবল হিসাবে পৃথকভাবে LB সামগ্রী টিপুন।

দুটি বিষয় বিবেচনা করা উচিত।

  1. খেয়াল করুন যেখানে আমি "পুশ অপারেটর * ফলাফলের সাথে যুক্ত" করেছি? আমরা এটিকে নিখুঁত গুণকে সুস্পষ্ট রূপান্তর করতে ব্যবহার করি। আপনি যদি ভেরিয়েবল হিসাবে আলাদাভাবে এলবির সামগ্রী খালি করে থাকেন তবে আপনাকে তাদের মধ্যে গুণক অপারেটর sertোকাতে হবে।
  2. ফাংশন লুপের শেষে, আমাদের মনে রাখতে হবে বাফারগুলিতে থাকা সমস্ত কিছু খালি রাখতে হবে।

কোড অনুবাদ করুন

সব মিলিয়ে আপনার টোকেনাইজ ফাংশনটি এখন দেখতে এইরকম হওয়া উচিত:

আমরা একটু ডেমো শুরু করতে পারি:

var টোকেন = টোকেনাইজ ("89sin (45) + 2.2x / 7"); tokens.forEach (ফাংশন (টোকেন, সূচক) {কনসোল.লগ (সূচক + "=>" + টোকেন.টাইপ + "(" + টোকেন.ভ্যালু + ")":});
হ্যাঁ! অন্তর্নিহিত গুণটির জন্য অতিরিক্ত * গুলি দ্রষ্টব্য

বোঁচকা

এটি আপনার বিন্যাসটি বিশ্লেষণ করে আপনি যা প্রত্যাশা করেন তার তুলনায় এটি কী করে তা পরিমাপ করে। নিজেকে এই জাতীয় প্রশ্ন জিজ্ঞাসা করুন, "ফাংশনটি যেমন ইচ্ছা মতো কাজ করে?" এবং "আমি কি সমস্ত প্রান্তিক মামলা কভার করেছি?"

প্রান্তিক ক্ষেত্রে এটি নেতিবাচক সংখ্যা এবং এর মতো হতে পারে। কার্যকারিতার জন্য তারা পরীক্ষাও চালায়। আপনি যদি শেষ পর্যন্ত সন্তুষ্ট হন তবে আপনি উন্নতির উপায়গুলি সন্ধান করতে পারেন।

পড়ার জন্য ধন্যবাদ। এই আইটেমটি সুপারিশ করতে দয়া করে ছোট্ট হৃদয়টি ক্লিক করুন এবং ভাল লাগলে শেয়ার করুন! এবং যদি আপনি গণিতের টোকেন তৈরির জন্য আলাদা পদ্ধতির চেষ্টা করে থাকেন তবে আমাকে মন্তব্যে জানাতে দিন।