লুপ জন্য একটি ভিতরে বিবৃতি এড়ানো?


116

আমার একটি ক্লাস আছে Writerযা এর writeVectorমত একটি ফাংশন রয়েছে :

void Drawer::writeVector(vector<T> vec, bool index=true)
{
    for (unsigned int i = 0; i < vec.size(); i++) {
        if (index) {
            cout << i << "\t";
        }
        cout << vec[i] << "\n";
    }
}

পারফরম্যান্স নিয়ে চিন্তিত অবস্থায় আমি একটি সদৃশ কোড না রাখার চেষ্টা করছি। ফাংশনে, আমি if (index)আমার- forলুপের প্রতিটি রাউন্ডে চেক করছি , যদিও ফলাফল সর্বদা একই থাকে। এটি "পারফরম্যান্স নিয়ে উদ্বেগ" এর বিরুদ্ধে is

আমি আমার forলুপের বাইরে চেক রেখে সহজেই এড়াতে পারতাম। তবে, আমি নকল কোডের প্রচুর পরিমাণে পেয়ে যাব:

void Drawer::writeVector(...)
{
    if (index) {
        for (...) {
            cout << i << "\t" << vec[i] << "\n";
        }
    }
    else {
        for (...) {
            cout << vec[i] << "\n";
        }
    }
}

সুতরাং এটি উভয়ই আমার জন্য "খারাপ" সমাধান। আমি যা ভাবছিলাম তা দুটি ব্যক্তিগত ফাংশন, এর মধ্যে একটি সূচককে ছাড়িয়ে যায় এবং তারপরে অন্যটিকে কল করে। অন্যটি কেবল মূল্য ছাড়িয়ে যায়। তবে, আমি কীভাবে এটি আমার প্রোগ্রামের সাথে ব্যবহার করব তা আমি বুঝতে পারি না, ifকোনটি কল করতে হবে তা দেখতে আমার এখনও চেক দরকার ...

সমস্যা অনুসারে বহুত্ববাদটি একটি সঠিক সমাধান বলে মনে হয়। তবে আমি এখানে এটি কীভাবে ব্যবহার করব তা দেখতে পাচ্ছি না। এই জাতীয় সমস্যা সমাধানের পছন্দের উপায় কী হবে?

এটি একটি আসল প্রোগ্রাম নয়, আমি এই ধরণের সমস্যাটি কীভাবে সমাধান করা উচিত তা শিখতে আগ্রহী।


8
@ জোনাথনরাইনহার্ট সম্ভবত কিছু লোক প্রোগ্রামিং শিখতে চান এবং সমস্যাগুলি কীভাবে সমাধান করবেন সে সম্পর্কে আগ্রহী?
স্কামাহ ওয়ান

9
আমি এই প্রশ্নটি +1 দিয়েছি। এই ধরণের অপ্টিমাইজেশন প্রায়শই প্রয়োজনীয় নাও হতে পারে তবে প্রথমত, এই সত্যটি দেখানো উত্তরের অংশ হতে পারে এবং দ্বিতীয়ত, বিরল ধরণের অপ্টিমাইজেশন এখনও প্রোগ্রামিংয়ের সাথে অত্যন্ত প্রাসঙ্গিক।
জোগোজাপান

31
প্রশ্নটি হ'ল ডিজাইন সম্পর্কিত যা কোড ডুপ্লিকেশন এবং লুপের ভিতরে জটিল যুক্তি এড়িয়ে চলে। এটি একটি ভাল প্রশ্ন, এটি হ্রাস করার দরকার নেই।
আলী

5
এটি একটি আকর্ষণীয় প্রশ্ন, সাধারণত সংকলকটির লুপ-ট্রান্সফর্মেশন পাসগুলি এটি খুব দক্ষতার সাথে সমাধান করবে। যদি ফাংশনটি এর মতো যথেষ্ট ছোট হয় তবে ইনলাইনার এটি সম্পর্কে যত্ন নেবে এবং সম্ভবত পুরোপুরি শাখাটি বন্ধ করে দেবে। আমি পরিবর্তে কোডটি পরিবর্তন করব যতক্ষণ না ইনলাইনার সারণিতে টেমপ্লেটগুলির সাথে সমাধানের চেয়ে কোডটি আনন্দের সাথে ইনলাইন করছে।
অ্যালেক্স

5
@ জোনাথনরাইনহার্ট: হু? প্রশ্নের প্রথম পুনর্বিবেচনা এটির সাথে কার্যত অভিন্ন। আপনার "আপনি যত্ন কেন?" মন্তব্যটি সমস্ত সংশোধনীর সাথে 100% অপ্রাসঙ্গিক । আপনাকে প্রকাশ্যে তিরস্কার করার জন্য - এটি কেবল আপনি নন, এখানে এখানকার অনেক লোকই এই সমস্যা সৃষ্টি করে। যখন শিরোনামটি "লুপের অভ্যন্তরে বিবৃতিগুলি এড়ানো হচ্ছে" , তখন প্রশ্নটি জেনেরিক, এটি উদাহরণস্বরূপ স্পষ্ট হওয়া উচিত এবং উদাহরণটি কেবল উদাহরণের জন্য । আপনি কাউকে সহায়তা করছেন না যখন আপনি প্রশ্নটিকে উপেক্ষা করবেন এবং ওপিকে বোকা দেখাবেন কারণ তিনি নির্দিষ্ট উদাহরণস্বরূপ উদাহরণটি ব্যবহার করেছেন।
ব্যবহারকারী541686

উত্তর:


79

ফ্যান্টেক্টর হিসাবে লুপের শরীরে পাস করুন। এটি সংকলন-সময় অন্তর্ভুক্ত হয়, কোনও পারফরম্যান্স পেনাল্টি নয়।

সি ++ স্ট্যান্ডার্ড লাইব্রেরিতে যা পরিবর্তিত হয় তা পাস করার ধারণা সর্বব্যাপী। একে কৌশল প্যাটার্ন বলা হয়

আপনি যদি C ++ 11 ব্যবহারের অনুমতি পেয়ে থাকেন তবে আপনি এই জাতীয় কিছু করতে পারেন:

#include <iostream>
#include <set>
#include <vector>

template <typename Container, typename Functor, typename Index = std::size_t>
void for_each_indexed(const Container& c, Functor f, Index index = 0) {

    for (const auto& e : c)
        f(index++, e);
}

int main() {

    using namespace std;

    set<char> s{'b', 'a', 'c'};

    // indices starting at 1 instead of 0
    for_each_indexed(s, [](size_t i, char e) { cout<<i<<'\t'<<e<<'\n'; }, 1u);

    cout << "-----" << endl;

    vector<int> v{77, 88, 99};

    // without index
    for_each_indexed(v, [](size_t , int e) { cout<<e<<'\n'; });
}

এই কোডটি নিখুঁত নয় তবে আপনি ধারণাটি পাবেন।

পুরানো সি ++ 98 এ এটির মতো দেখাচ্ছে:

#include <iostream>
#include <vector>
using namespace std;

struct with_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << i << '\t' << e << '\n';
  }
};

struct without_index {
  void operator()(ostream& out, vector<int>::size_type i, int e) {
    out << e << '\n';
  }
};


template <typename Func>
void writeVector(const vector<int>& v, Func f) {
  for (vector<int>::size_type i=0; i<v.size(); ++i) {
    f(cout, i, v[i]);
  }
}

int main() {

  vector<int> v;
  v.push_back(77);
  v.push_back(88);
  v.push_back(99);

  writeVector(v, with_index());

  cout << "-----" << endl;

  writeVector(v, without_index());

  return 0;
}

আবার কোডটি নিখুঁত থেকে দূরে তবে এটি আপনাকে ধারণা দেয়।


4
for(int i=0;i<100;i++){cout<<"Thank you!"<<endl;}: ডি এটি আমি যে ধরণের সমাধানের সন্ধান করছিলাম এটি এটি আকর্ষণীয় কাজ করে :) আপনি কয়েকটি মন্তব্য দিয়ে এটি উন্নতি করতে পারেন (প্রথমে এটি বুঝতে সমস্যা হয়েছিল), তবে আমার তাতে কোনও সমস্যা হয়নি :)
স্কামাহ ওয়ান

1
আমি আনন্দিত এটি সাহায্য করেছে! দয়া করে আমার আপডেটটি সি ++ 11 কোড দিয়ে দেখুন, এটি সি ++ 98 সংস্করণের তুলনায় কম ফুলে গেছে।
আলী

3
নিতপিক: এটি ওপির উদাহরণের ক্ষেত্রে ঠিক আছে কারণ লুপের দেহটি এত ছোট, তবে এটি যদি বড় হয় (কেবলমাত্র এককটির পরিবর্তে কয়েক ডজন লাইন কোডের কল্পনা করুন cout << e << "\n";) তখনও বেশ কিছু কোডের নকল থাকতে পারে।
সিয়াম

3
কাঠামো এবং অপারেটর ওভারলোডিং কেন সি ++ 03 উদাহরণে ব্যবহার করা হয়? কেন কেবল দুটি ফাংশন করবেন না এবং পয়েন্টারগুলি তাদের কাছে দিন?
ম্যালকম

2
নিবন্ধন করুন যদি তারা কাঠামোগত হয় তবে সম্ভাবনা হ'ল ফাংশন কলগুলি ইনলাইন করা যায়। যদি আপনি কোনও ফাংশন পয়েন্টারটি পাস করেন তবে সম্ভাবনাগুলি হ'ল সেই কলগুলি ইনলাইন করা যায় না
আলী

40

ফাংশনে, আমি ফলাফলটি সর্বদা একই থাকলেও, যদি আমার ফর-লুপের প্রতিটি রাউন্ডে (সূচক) পরীক্ষা করে যাচ্ছি। এটি "পারফরম্যান্স নিয়ে উদ্বেগ" এর বিরুদ্ধে is

যদি সত্যই এটি হয় তবে শাখার ভবিষ্যদ্বাণীকারীকে (ধ্রুবক) ফলাফলটি ভবিষ্যদ্বাণী করতে কোনও সমস্যা হবে না। যেমন, এটি প্রথম কয়েকটি পুনরাবৃত্তিতে ভুল-অনুমানের জন্য কেবল একটি হালকা ওভারহেডের কারণ ঘটবে। পারফরম্যান্সের দিক থেকে এটি উদ্বিগ্ন হওয়ার কিছু নেই

এই ক্ষেত্রে আমি স্পষ্টতার জন্য পরীক্ষাটি লুপের ভিতরে রাখার পক্ষে পরামর্শ করছি।


3
এটি কেবল একটি উদাহরণ, এই ধরণের সমস্যাটি কীভাবে সমাধান করা উচিত তা শিখতে আমি এখানে আছি। আমি কেবল কৌতূহলী, এমনকি একটি বাস্তব প্রোগ্রাম তৈরি করছি না। প্রশ্নে এটি উল্লেখ করা উচিত ছিল।
স্কামাহ ওয়ান

40
সেক্ষেত্রে মনে রাখবেন যে অকালীন অপটিমাইজেশন হ'ল সমস্ত মন্দের মূল । প্রোগ্রামিং করার সময়, সর্বদা কোড পঠনযোগ্যতার দিকে মনোনিবেশ করুন এবং নিশ্চিত করুন যে আপনি কী করতে চাইছেন তা অন্যরা বুঝতে পারে। আপনার প্রোগ্রামটি প্রোফাইল দেওয়ার পরে এবং হটস্পটগুলি সনাক্ত করার পরে কেবলমাত্র মাইক্রো অপ্টিমাইজেশান এবং বিভিন্ন হ্যাকগুলি বিবেচনা করুন । আপনার প্রয়োজনীয়তা স্থাপন না করে আপনার কখনই অপ্টিমাইজেশন বিবেচনা করা উচিত নয়। খুব প্রায়ই, পারফরম্যান্সের সমস্যাগুলি আপনি যেখানে সেগুলি প্রত্যাশা করে তা হয় না।
মার্ক ক্লেসেন

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

8
আপনি ধরে নিচ্ছেন যে কোডটি এমন একটি প্রসেসরে চলছে যার শাখার পূর্বাভাস দিয়ে শুরু হবে। বেশিরভাগ সিস্টেমে সি ++ চলছে (যদিও, সম্ভবত বেশিরভাগ সিস্টেমে একটি দরকারী std::cout
ডু

2
-1। হ্যাঁ, শাখার পূর্বাভাস এখানে ভাল কাজ করবে। হ্যাঁ, কম্পাইলারটি শর্তটি আসলে লুপের বাইরে উত্তোলন করতে পারে। হ্যাঁ, পোত্রোএই কিন্তু একটি লুপ মধ্যে শাখা হয় বিপজ্জনক জিনিস যে প্রায়ই কর্মক্ষমতা প্রভাব রয়েছে যদি কেউ সত্যিই বজায় রাখে কর্মক্ষমতা সম্পর্কে একটি ভাল উপদেশ, এবং আমি শুধু "শাখা ভবিষ্যদ্বাণী" বলার মাধ্যমে সেই খারিজ মনে করি না হয়। সর্বাধিক উল্লেখযোগ্য উদাহরণ হ'ল একটি ভেক্টরাইজিং সংকলকটি এটি পরিচালনা করতে পূর্বাভাসের প্রয়োজন হবে , শাখা-কম লুপগুলির চেয়ে কম দক্ষ কোড তৈরি করে।
ওক

35

আলীর জবাবটি প্রসারিত করতে, যা পুরোপুরি সঠিক তবে এখনও কিছু কোড নকল করে (লুপের অংশের অংশ, কৌশলটি প্যাটার্ন ব্যবহার করার সময় এটি দুর্ভাগ্যক্রমে খুব কমই এড়ানো যায়) ...

নির্দিষ্ট ক্ষেত্রে কোড ডুপ্লিকেশন খুব বেশি নয় তবে এটি আরও কমানোর একটি উপায় রয়েছে যা কার্যকরী বডিটি কয়েকটি নির্দেশাবলীর চেয়ে বড় হলে কার্যকরী হয়

কীটি হ'ল ধ্রুবক ভাঁজ / ডেড কোড নির্মূলকরণের জন্য সংকলকটির ক্ষমতাটি ব্যবহার করা । আমরা রানাইল টাইমকে indexএকটি সংকলন-সময় মানকে ম্যানুয়ালি ম্যাপ করার মাধ্যমে এটি করতে পারি (কেবলমাত্র যখন সীমাবদ্ধ সংখ্যক কেস থাকে - তখন করা সহজ - এই ক্ষেত্রে দুটি) এবং একটি টাইপবিহীন টেম্পলেট যুক্তি ব্যবহার করে যা সংকলনে পরিচিত -time:

template<bool index = true>
//                  ^^^^^^ note: the default value is now part of the template version
//                         see below to understand why
void writeVector(const vector<int>& vec) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (index) { // compile-time constant: this test will always be eliminated
            cout << i << "\t"; // this will only be kept if "index" is true
        }
        cout << vec[i] << "\n";
    }
}

void writeVector(const vector<int>& vec, bool index)
//                                            ^^^^^ note: no more default value, otherwise
//                                            it would clash with the template overload
{
    if (index) // runtime decision
        writeVector<true>(vec);
        //          ^^^^ map it to a compile-time constant
    else
        writeVector<false>(vec);
}

এইভাবে আমরা সংকলিত কোডটি শেষ করি যা আপনার দ্বিতীয় কোড উদাহরণ (বহিরাগত if/ অভ্যন্তরীণ for) এর সমতুল্য তবে কোডটি নিজেরাই নকল না করে। এখন আমরা writeVectorযতটা জটিল তার টেম্পলেট সংস্করণটিকে তৈরি করতে পারি, বজায় রাখার জন্য সর্বদা কোডের একক অংশ থাকবে।

টেমপ্লেট সংস্করণ (যা একটি নন-টাইপ টেম্পলেট আর্গুমেন্টের আকারে একটি সংকলন-সময় ধ্রুবক গ্রহণ করে) এবং নন-টেম্পলেট সংস্করণ (যা একটি ফাংশন আর্গুমেন্ট হিসাবে একটি রানটাইম ভেরিয়েবল গ্রহণ করে) ওভারলোড হয় ed এটি আপনার প্রয়োজনের উপর নির্ভর করে সর্বাধিক প্রাসঙ্গিক সংস্করণ বাছাই করতে দেয়, উভয় ক্ষেত্রেই একইরকম, সিনট্যাক্স মনে রাখা সহজ:

writeVector<true>(vec);   // you already know at compile-time which version you want
                          // no need to go through the non-template runtime dispatching

writeVector(vec, index);  // you don't know at compile-time what "index" will be
                          // so you have to use the non-template runtime dispatching

writeVector(vec);         // you can even use your previous syntax using a default argument
                          // it will call the template overload directly

2
দয়া করে মনে রাখবেন আপনি লুপটির ভিতরে যুক্তিকে আরও জটিল করে তোলার ব্যয়ে কোডের সদৃশটি সরিয়ে ফেলেছেন। আমি এই বিশেষ সাধারণ উদাহরণটির জন্য প্রস্তাবিতের চেয়ে ভাল বা খারাপ দেখতে পাচ্ছি না। যাইহোক +1!
আলী

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

1
@ ক্রিসস: আসলে আমার আগের সমাধানটি ইতিমধ্যে অনুমতি দিয়েছে যে আপনি যদি doWriteVectorসরাসরি ডেকেছিলেন তবে আমি সম্মত হন নামটি দুর্ভাগ্যজনক। আমি সবেমাত্র এটিকে দুটি ওভারলোডেড writeVectorফাংশন (একটি টেমপ্লেট, অন্যটি একটি নিয়মিত ফাংশন) হিসাবে পরিবর্তন করেছি যাতে ফলাফলটি আরও একজাতীয় হয়। পরামর্শের জন্য ধন্যবাদ. ;)
সায়াম

4
আইএমও এটি সেরা উত্তর। +1
ব্যবহারকারী541686

1
@ মেহরদাদ বাদে এটি মূল প্রশ্নের উত্তর দেয় না লুপের ভিতরে বিবৃতি যদি এড়ানো হয়? পারফরম্যান্স পেনাল্টি এড়াতে কীভাবে এটি উত্তর দেয় answer "অনুলিপি" হিসাবে, কীভাবে এটি সর্বোত্তমভাবে সুনির্দিষ্টভাবে প্রমাণিত হয়েছে তা দেখার জন্য ব্যবহারের ক্ষেত্রে আরও বাস্তব উদাহরণের প্রয়োজন হবে। যেমনটি আমি আগেই বলেছি, আমি এই উত্তরটিকে অগ্রাহ্য করেছি।
আলী

0

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

#include <cstdio>
#include <iterator>

void write_vector(int* begin, int* end, bool print_index = false) {
    unsigned index = 0;
    for(int* it = begin; it != end; ++it) {
        if (print_index) {
            std::printf("%d: %d\n", index, *it);
        } else {
            std::printf("%d\n", *it);
        }
        ++index;
    }
}

int my_vector[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};


int main(int argc, char** argv) {
    write_vector(std::begin(my_vector), std::end(my_vector));
}

আমি এটি সঙ্কলন করতে নিম্নলিখিত কমান্ড লাইনটি ব্যবহার করছি:

g++ --version
g++ (GCC) 4.9.1
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
g++ -O3 -std=c++11 main.cpp

তারপরে, আসল সমাবেশ ডাম্প করা যাক:

objdump -d a.out | c++filt > main.s

এর ফলাফল সমাবেশ write_vector:

00000000004005c0 <write_vector(int*, int*, bool)>:
  4005c0:   48 39 f7                cmp    %rsi,%rdi
  4005c3:   41 54                   push   %r12
  4005c5:   49 89 f4                mov    %rsi,%r12
  4005c8:   55                      push   %rbp
  4005c9:   53                      push   %rbx
  4005ca:   48 89 fb                mov    %rdi,%rbx
  4005cd:   74 25                   je     4005f4 <write_vector(int*, int*, bool)+0x34>
  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>
  4005d3:   31 ed                   xor    %ebp,%ebp
  4005d5:   0f 1f 00                nopl   (%rax)
  4005d8:   8b 13                   mov    (%rbx),%edx
  4005da:   89 ee                   mov    %ebp,%esi
  4005dc:   31 c0                   xor    %eax,%eax
  4005de:   bf a4 06 40 00          mov    $0x4006a4,%edi
  4005e3:   48 83 c3 04             add    $0x4,%rbx
  4005e7:   83 c5 01                add    $0x1,%ebp
  4005ea:   e8 81 fe ff ff          callq  400470 <printf@plt>
  4005ef:   49 39 dc                cmp    %rbx,%r12
  4005f2:   75 e4                   jne    4005d8 <write_vector(int*, int*, bool)+0x18>
  4005f4:   5b                      pop    %rbx
  4005f5:   5d                      pop    %rbp
  4005f6:   41 5c                   pop    %r12
  4005f8:   c3                      retq   
  4005f9:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
  400600:   8b 33                   mov    (%rbx),%esi
  400602:   31 c0                   xor    %eax,%eax
  400604:   bf a8 06 40 00          mov    $0x4006a8,%edi
  400609:   48 83 c3 04             add    $0x4,%rbx
  40060d:   e8 5e fe ff ff          callq  400470 <printf@plt>
  400612:   49 39 dc                cmp    %rbx,%r12
  400615:   75 e9                   jne    400600 <write_vector(int*, int*, bool)+0x40>
  400617:   eb db                   jmp    4005f4 <write_vector(int*, int*, bool)+0x34>
  400619:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)

আমরা দেখতে পাচ্ছি যে ফাংশনটির ভিক্ষাবৃত্তিতে আমরা মানটি পরীক্ষা করি এবং দুটি সম্ভাব্য লুপের মধ্যে একটিতে ঝাঁপিয়ে পড়ে:

  4005cf:   84 d2                   test   %dl,%dl
  4005d1:   74 2d                   je     400600 <write_vector(int*, int*, bool)+0x40>

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

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.