স্মৃতিচারণ কী কী এবং আমি পাইথনে এটি কীভাবে ব্যবহার করতে পারি?


378

আমি কেবল পাইথন শুরু করেছি এবং স্মৃতিচারণ কী এবং এটি কীভাবে ব্যবহার করতে হয় সে সম্পর্কে আমার কোনও ধারণা নেই । এছাড়াও, আমি কি একটি সরল উদাহরণ দিতে পারি?


215
প্রাসঙ্গিক উইকিপিডিয়া নিবন্ধের দ্বিতীয় বাক্যে যখন একটি সাধারণ শীর্ষ-ডাউন পার্সিং অ্যালগরিদম [2] [3] এর মধ্যে পারস্পরিক-পুনরাবৃত্ত বংশোদ্ভূত বংশবৃদ্ধি [2] [3] বাক্যাংশ থাকে যা বহুবর্ষীয় সময় ও স্থানের ক্ষেত্রে অস্পষ্টতা এবং বাম পুনরাবৃত্তি সমন্বিত করে "আমি মনে করি এটি কী চলছে তা জিজ্ঞাসা করা পুরোপুরি উপযুক্ত।
ক্লুলেস

10
@ ক্লুহলেস: এই বাক্যাংশটির আগে "মেমোয়েজেশন অন্যান্য প্রসঙ্গে (এবং গতি অর্জন ব্যতীত অন্য উদ্দেশ্যে) যেমন ব্যবহার করা হয়েছে" তেও ব্যবহৃত হয়েছে। সুতরাং এটি কেবল উদাহরণগুলির একটি তালিকা (এবং বোঝার দরকার নেই); এটি স্মৃতিচারণের ব্যাখ্যার অংশ নয়।
শ্রীভাতসার আর

1
এই টুইটটি মারা গেছেন আপনি দয়া করে একটি আপডেট খুঁজে পেতে পারেন?
জেএস।

2
পিআইডিএফ ফাইলের নতুন লিঙ্ক, যেহেতু পাইকোগ্সিআইএনআইপিও ডাউন রয়েছে: people.ucsc.edu/~abrsvn/NLTK_parsing_েমোস.পিডিএফ
স্টিফান

4
@Clueless, নিবন্ধ আসলে বলে " সহজ পারস্পরিক-রিকার্সিভ বংশদ্ভুত পার্সিং [1] একটি সাধারণ টপ-ডাউন অ্যালগরিদম [2] [3] যে বহুপদী সময় এবং স্থান অস্পষ্টতা এবং বাম পুনরাবৃত্তির accommodates পার্স মধ্যে"। আপনি মিস সহজ , যা স্পষ্টত উদাহরণস্বরূপ অনেক পরিষ্কার :) তোলে।
স্টাডিজিক

উত্তর:


353

মেমোয়েজেশন কার্যকরভাবে স্মরণকে বোঝায় ("স্মৃতিচারণ" → "স্মারকলিপি" remembered মনে রাখতে হবে) পদ্ধতি ইনপুটগুলির উপর ভিত্তি করে মেথড কলগুলির ফলাফল এবং তারপরে পুনরায় ফলাফল গণনা করার পরিবর্তে স্মরণ করা ফলাফল ফিরে আসে। আপনি এটিকে পদ্ধতির ফলাফলের ক্যাশে হিসাবে ভাবতে পারেন। আরও তথ্যের জন্য, আলগোরিদিম পরিচয় (3 ই) এর সংজ্ঞা সংজ্ঞাটির জন্য 387 পৃষ্ঠা দেখুন , Cormen এট আল।

পাইথনে মেমোয়েজেশন ব্যবহার করে ফ্যাক্টরিয়ালগুলি গণনা করার জন্য একটি সাধারণ উদাহরণ এটি হতে পারে:

factorial_memo = {}
def factorial(k):
    if k < 2: return 1
    if k not in factorial_memo:
        factorial_memo[k] = k * factorial(k-1)
    return factorial_memo[k]

আপনি আরও জটিল হয়ে উঠতে পারেন এবং মেমোয়াইজেশন প্রক্রিয়াটিকে একটি শ্রেণিতে অন্তর্ভুক্ত করতে পারেন:

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        #Warning: You may wish to do a deepcopy here if returning objects
        return self.memo[args]

তারপর:

def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

factorial = Memoize(factorial)

পাইথন ২.৪-এ " সজ্জাকারী " হিসাবে পরিচিত একটি বৈশিষ্ট্য যুক্ত হয়েছিল যা আপনাকে একই জিনিসটি সম্পাদন করার জন্য কেবল নিম্নলিখিতটি লিখতে দেয়:

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

পাইথন প্রসাধক লাইব্রেরী একটি অনুরূপ প্রসাধক বলা memoizedসামান্য চেয়ে আরো জোরালো যে Memoizeবর্গ দেখানো হচ্ছে না।


2
এই পরামর্শের জন্য ধন্যবাদ। মেমোইজ ক্লাসটি একটি মার্জিত সমাধান যা খুব বেশি রিপ্যাক্টেরিংয়ের প্রয়োজন ছাড়াই সহজে বিদ্যমান কোডে প্রয়োগ করা যেতে পারে।
ক্যাপ্টেন লেপটন

10
মেমোবাইজ শ্রেণি সমাধানটি বগি, এটি এর মতো কাজ করবে না factorial_memo, কারণ factorialঅভ্যন্তর def factorialএখনও পুরানো স্মরণকে কল করে না factorial
অ্যাডামস্মিথ

9
যাইহোক, আপনি আরও লিখতে পারেন if k not in factorial_memo:, যা পড়ার চেয়ে ভাল if not k in factorial_memo:
শ্রীভাতসার আর

5
সত্যই এটি একটি সাজসজ্জা হিসাবে করা উচিত।
এমিলিন ও'রেগান

3
@ durden2.0 আমি জানি এটি একটি পুরানো মন্তব্য, তবে argsএটি একটি টিপল। def some_function(*args)আরগসকে একটি টিপল করে তোলে।
অ্যাডাম স্মিথ

232

পাইথন 3.2 এ নতুন functools.lru_cache। ডিফল্টরূপে, এটি শুধুমাত্র 128 অতি সম্প্রতি যেটি ব্যবহার কল ক্যাশে, কিন্তু আপনি সেট করতে পারেন maxsizeকরার Noneনির্দেশ করে ক্যাশে এর মেয়াদ শেষ না করা উচিত:

import functools

@functools.lru_cache(maxsize=None)
def fib(num):
    if num < 2:
        return num
    else:
        return fib(num-1) + fib(num-2)

এই ফাংশনটি নিজেই খুব ধীর, চেষ্টা করুন fib(36)এবং আপনাকে প্রায় দশ সেকেন্ড অপেক্ষা করতে হবে।

যোগ করার পদ্ধতি lru_cacheটীকা নিশ্চিত করে যে যদি ফাংশন একটি নির্দিষ্ট মান জন্য সম্প্রতি বলা হয়েছে, এটা যে মান recompute করবে না, কিন্তু একটি ক্যাশে পূর্ববর্তী ফলাফলের ব্যবহার করুন। এই ক্ষেত্রে, এটি একটি দুর্দান্ত গতির উন্নতির দিকে পরিচালিত করে, যখন কোডটি ক্যাশিংয়ের বিশদ নিয়ে বিশৃঙ্খলাযুক্ত নয়।


2
ট্রাইড ফাইব (1000), পুনরাবৃত্তির ত্রুটি পেয়েছে: তুলনায় সর্বাধিক পুনরাবৃত্তির গভীরতা ছাড়িয়েছে
এক্স Æ এ-12

5
@ অ্যান্ডিক ডিফল্ট পাই 3 রিকার্সনের সীমাটি 1000 The প্রথমবার fibবলা হয়, স্মৃতিচারণ হওয়ার আগে এটি বেস কেসে পুনরাবৃত্তি করতে হবে। সুতরাং, আপনার আচরণটি প্রায় প্রত্যাশিত।
কোয়েলক্লেফ

1
আমি যদি ভুল না হয়ে থাকি তবে প্রক্রিয়াটি মারা না যাওয়া অবধি এটি ক্যাশে করে ফেলেছে, তাই না? বা প্রক্রিয়াটি হত্যা করা হয়েছে কিনা তা বিবেচনা না করেই তা ক্যাশে করে? পছন্দ করুন, বলুন আমি আমার সিস্টেম পুনরায় চালু করি - ক্যাশেড ফলাফলগুলি কি এখনও ক্যাশে হবে?
ক্রিস্টাদা 673

1
@ ক্রিস্টাড 673৩ হ্যাঁ, এটি ডিস্কে নয়, প্রক্রিয়াটির স্মৃতিতে সঞ্চিত।
ফ্লাইম

2
নোট করুন যে এটি ফাংশনের প্রথম রানকেও গতি দেয়, যেহেতু এটি একটি পুনরাবৃত্ত ফাংশন এবং এর নিজস্ব মধ্যবর্তী ফলাফলগুলি ক্যাশে করছে। আমার মতো ডামিগুলিকে আরও স্পষ্ট করে তুলতে অভ্যস্তভাবে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ধীরে ফিরে আসা এমন কোনও ফাংশন চিত্রিত করতে পারে। : ডি
এন্ডোলিথ

61

অন্যান্য উত্তরগুলি এটি বেশ ভাল কি কভার করে। আমি যে পুনরাবৃত্তি করছি না। আপনার পক্ষে কার্যকর হতে পারে এমন কয়েকটি পয়েন্ট।

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

memoised_function = memoise(actual_function)

বা একটি সজ্জা হিসাবে প্রকাশ করা

@memoise
def actual_function(arg1, arg2):
   #body

18

স্মৃতিচারণ ব্যয়বহুল গণনার ফলাফল রাখছে এবং ক্রমাগত পুনরায় গণনার চেয়ে ক্যাশেড ফলাফলটি ফিরিয়ে দিচ্ছে।

এখানে একটি উদাহরণ:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]

স্মৃতিচারণের উইকিপিডিয়া এন্ট্রিতে আরও একটি সম্পূর্ণ বিবরণ পাওয়া যাবে ।


হুম, এখন যদি পাইথনটি সঠিক হত তবে এটি কাঁপতে পারে, তবে এটি মনে হয় না ... ঠিক আছে, তাই "ক্যাশে" ডিক নয়? কারণ যদি এটি হয় তবে এটি হওয়া উচিত if input not in self.cache এবং self.cache[input] ( has_keyঅপ্রচলিত যেহেতু ... ২.x সিরিজের শুরুর দিকে, ২.০ না হলে self.cache(index)কখনওই সঠিক ছিল না। আইআইআরসি)
এ। এরহার্ড

15

hasattrযারা হস্তশিল্প করতে চান তাদের জন্য বিল্ট-ইন ফাংশনটি ভুলে যাওয়া যাক না । এইভাবে আপনি ফাংশন সংজ্ঞায়নের (মেমরি ক্যাশে রাখতে পারেন বিশ্বব্যাপী বিপরীতে)।

def fact(n):
    if not hasattr(fact, 'mem'):
        fact.mem = {1: 1}
    if not n in fact.mem:
        fact.mem[n] = n * fact(n - 1)
    return fact.mem[n]

এটি একটি খুব ব্যয়বহুল ধারণা মত মনে হচ্ছে। প্রতিটি এন এর জন্য, এটি কেবল n এর জন্য ফলাফলকেই ক্যাশে করে না, তবে 2 ... এন -1 এর জন্যও ফলাফলকে ক্যাশে করে।
কোডফোরস্টার

15

আমি এটি অত্যন্ত দরকারী খুঁজে পেয়েছি

def memoize(function):
    from functools import wraps

    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper


@memoize
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci(25)

দেখুন docs.python.org/3/library/functools.html#functools.wraps কেন এক ব্যবহার করা উচিত জন্য functools.wraps
আনিশপেটেল

1
memoমেমোরিটি যাতে মুক্ত হয় তাই আমাকে কী ম্যানুয়ালি সাফ করার দরকার আছে ?
টি

পুরো ধারণাটি হ'ল ফলাফলগুলি একটি সেশনের মধ্যে মেমোর ভিতরে সংরক্ষণ করা হয়। অর্থাৎ কিছুই হ'ল সাফ হচ্ছে না
mr.bjerre

6

স্মৃতিচারণ মূলত পুনরাবৃত্ত আলগোরিদিমগুলির সাথে সম্পন্ন অতীতের ক্রিয়াকলাপগুলির ফলাফলগুলি সংরক্ষণ করছে যাতে পরবর্তী পর্যায়ে যদি একই গণনা প্রয়োজন হয় তবে পুনরাবৃত্তি গাছটি অতিক্রম করার প্রয়োজনীয়তা হ্রাস করতে।

দেখতে http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

পাইথনে ফিবোনাচি স্মৃতিচারণের উদাহরণ:

fibcache = {}
def fib(num):
    if num in fibcache:
        return fibcache[num]
    else:
        fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
        return fibcache[num]

2
আরও পারফরম্যান্সের জন্য প্রথম কিছু জ্ঞাত মানগুলির সাথে আপনার ফাইব্যাশে প্রাক-বীজ তৈরি করুন, তারপরে আপনি কোডটির 'হট পাথ' থেকে তাদের পরিচালনা করার জন্য অতিরিক্ত যুক্তি নিতে পারেন।
jkflying

5

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


5

ভাল আমার প্রথম অংশটি উত্তর দেওয়া উচিত: স্মৃতিচারণ কী?

সময়ের জন্য স্মৃতি বাণিজ্য করার এটি কেবল একটি পদ্ধতি। গুণন সারণীর কথা ভাবেন ।

পাইথনে ডিফল্ট মান হিসাবে পরিবর্তনীয় অবজেক্ট ব্যবহার করা সাধারণত খারাপ হিসাবে বিবেচিত হয়। তবে যদি এটি বুদ্ধিমানের সাথে ব্যবহার করে তবে এটি বাস্তবায়নের ক্ষেত্রে কার্যকর হতে পারে memoization

Http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects থেকে গৃহীত একটি উদাহরণ এখানে

dictফাংশন সংজ্ঞাতে একটি পরিবর্তনীয় ব্যবহার করে , মধ্যবর্তী গণিত ফলাফলগুলি ক্যাশে করা যায় (যেমন গণনার factorial(10)পরে গণনা করার সময় factorial(9), আমরা সমস্ত মধ্যবর্তী ফলাফল পুনরায় ব্যবহার করতে পারি)

def factorial(n, _cache={1:1}):    
    try:            
        return _cache[n]           
    except IndexError:
        _cache[n] = factorial(n-1)*n
        return _cache[n]

4

এখানে এমন একটি সমাধান রয়েছে যা শুকনো না করে তালিকা বা ডিক্ট ধরণের আর্গুমেন্টের সাথে কাজ করবে:

def memoize(fn):
    """returns a memoized version of any function that can be called
    with the same list of arguments.
    Usage: foo = memoize(foo)"""

    def handle_item(x):
        if isinstance(x, dict):
            return make_tuple(sorted(x.items()))
        elif hasattr(x, '__iter__'):
            return make_tuple(x)
        else:
            return x

    def make_tuple(L):
        return tuple(handle_item(x) for x in L)

    def foo(*args, **kwargs):
        items_cache = make_tuple(sorted(kwargs.items()))
        args_cache = make_tuple(args)
        if (args_cache, items_cache) not in foo.past_calls:
            foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
        return foo.past_calls[(args_cache, items_cache)]
    foo.past_calls = {}
    foo.__name__ = 'memoized_' + fn.__name__
    return foo

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

if is_instance(x, set):
    return make_tuple(sorted(list(x)))

1
দুর্দান্ত চেষ্টা। ঝকঝকে না করে একটি listযুক্তি [1, 2, 3]ভুল করে setমান হিসাবে একটি পৃথক যুক্তি হিসাবে একই হিসাবে বিবেচনা করা যেতে পারে {1, 2, 3}। এছাড়াও, সেটগুলি অভিধানের মতো আনর্ডার্ড হয় তাই সেগুলিও হওয়া দরকার sorted()। এছাড়াও মনে রাখবেন যে একটি পুনরাবৃত্ত তথ্য কাঠামো যুক্তি অসীম লুপের কারণ হতে পারে।
মার্টিনিউ

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

আমি যে সমস্যাটি উল্লেখ করেছি তা হ'ল listএস এবং setএস একই জিনিসকে "টিপলাইজড" করা হয়েছে এবং তাই একে অপরের থেকে পৃথক হয়ে যায়। setsআপনার সর্বশেষ আপডেটে বর্ণিত সমর্থনগুলির জন্য উদাহরণ কোডটি এড়াতে পারে না যে আমি ভীত। এটি সহজেই আলাদাভাবে পাস করার মাধ্যমে [1,2,3]এবং {1,2,3}"মেমোইজ" ডি টেস্ট ফাংশনের যুক্তিরূপে এবং এটি দু'বার বলা হয়েছে কিনা, এটি যেমন হওয়া উচিত, তা নয় কিনা তা সহজেই দেখা যায় ।
মার্টিনিউ

হ্যাঁ, আমি এই সমস্যাটি পড়েছি, তবে আমি এটিকে সমাধান করতে পারি নি কারণ আমি মনে করি এটি আপনি উল্লিখিত অন্যটির তুলনায় অনেক বেশি ছোটখাটো। আপনি কখন শেষবারের মতো কোনও স্মৃতি ফাংশন লিখেছিলেন যেখানে কোনও স্থির যুক্তি তালিকার তালিকা বা সেট হতে পারে এবং দু'জনের ফলাফল আলাদা আউটপুট হতে পারে? যদি আপনি এরকম বিরল ক্ষেত্রে চালিত হন, আপনি আবার হ্যান্ডেল_ইটিমটি পুনরায় লেখার জন্য আবার লিখতে হবে, উপাদানটি যদি সেট হয় তবে 0 বা একটি তালিকা যদি 1 থাকে।
রাসেল স্টাওয়ার্ট

আসলে, সেখানে সঙ্গে একটি অনুরূপ সমস্যা lists এবং dictগুলি কারণ এটা সম্ভব একটি জন্য listএটা ঠিক একই জিনিস যে কলিং থেকে প্রসূত আছে make_tuple(sorted(x.items()))একটি অভিধান জন্য। উভয় ক্ষেত্রেই একটি সহজ সমাধান type()হ'ল উত্পন্ন টিউপলের মধ্যে মানটির অন্তর্ভুক্ত করা। আমি বিশেষত ওগুলি হ্যান্ডেল করার জন্য এমনকি আরও সহজ উপায় সম্পর্কে ভাবতে পারি set, তবে এটি সাধারণ হয় না।
মার্টিনো

3

সমাধান যা কীওয়ার্ড আরগগুলি পাস করা হয়েছে ( ইন্সপেক্ট.জেটারগ্যাস্পেক ব্যবহার করে ) স্বাধীনভাবে ক্রমানুসারে অবস্থান এবং কীওয়ার্ড উভয় যুক্তির সাথে কাজ করে :

import inspect
import functools

def memoize(fn):
    cache = fn.cache = {}
    @functools.wraps(fn)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(zip(inspect.getargspec(fn).args, args)))
        key = tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
        if key not in cache:
            cache[key] = fn(**kwargs)
        return cache[key]
    return memoizer

অনুরূপ প্রশ্ন: সমমানের ভারার্গস ফাংশন সনাক্তকরণকে পাইথনে স্মৃতিচারণের আহ্বান জানানো হয়


2
cache = {}
def fib(n):
    if n <= 1:
        return n
    else:
        if n not in cache:
            cache[n] = fib(n-1) + fib(n-2)
        return cache[n]

4
আপনি কেবল if n not in cacheপরিবর্তে ব্যবহার করতে পারেন । ব্যবহার cache.keysঅজগর 2
n611x007

2

ইতিমধ্যে সরবরাহ করা উত্তরগুলিতে কেবল যুক্ত করতে চেয়েছিলেন, পাইথন ডেকোরেটর লাইব্রেরিতে কিছু সাধারণ এখনও কার্যকর কার্যকর বাস্তবায়ন রয়েছে যা "অপ্রয়োজনীয় প্রকারগুলি" স্মরণও করতে পারে, বিপরীত functools.lru_cache


1
এই সাজসজ্জারটি "অশ্রাব্য প্রকারগুলি" স্মরণ করে না ! এটি কেবল স্মৃতিচারণ ছাড়াই ফাংশনটি কল করার পিছনে পড়ে, স্পষ্টের বিরুদ্ধে লড়াই করা অন্তর্নিহিত মতবাদের চেয়ে ভাল
ostrokach
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.