পাইথন হ্যাশেবল ডিক্টস


94

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

সমস্যাটি হ'ল পাইথন ডিক্টস অন্যান্য ডিক্টের চাবি হিসাবে উপস্থিত হতে পারে না। এমনকি একটি টিপল ব্যবহার করা (যেমনটি আমি যেভাবেই করবো) কোনও সাহায্য করে না।

>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

আমার ধারণা এটি পুরোপুরি টিপলস হতে হবে। এখন পাইথন স্ট্যান্ডার্ড লাইব্রেরিতে আমার যা প্রয়োজন তা প্রায় সরবরাহ করে, collections.namedtupleখুব আলাদা সিনট্যাক্স রয়েছে তবে এটি কী হিসাবে ব্যবহার করা যেতে পারে। উপরের অধিবেশন থেকে অব্যাহত:

>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}

ঠিক আছে. তবে যে নিয়মটি আমি ব্যবহার করতে চাইছি তার প্রতিটি সম্ভাব্য সংমিশ্রণের জন্য আমাকে একটি ক্লাস তৈরি করতে হবে, যা এতটা খারাপ নয়, কারণ প্রতিটি পার্স নিয়ম জানে যে এটি ঠিক কী পরামিতি ব্যবহার করে, তাই ক্লাস একই সময়ে সংজ্ঞায়িত করা যায় নিয়মকে বিশ্লেষণ করে এমন ফাংশন হিসাবে।

সম্পাদনা: এসগুলির সাথে একটি অতিরিক্ত সমস্যা namedtupleহ'ল তারা কঠোরভাবে অবস্থানগত। দুটি টিপল যা দেখতে দেখতে আলাদা হতে হবে তা আসলে একই হতে পারে:

>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False

tl'dr: আমি কীভাবে এস পাব যা অন্যগুলির dictকী হিসাবে ব্যবহার করা যেতে পারে dict?

উত্তরে কিছুটা হ্যাক করা, আমি ব্যবহার করছি আরও সম্পূর্ণ সমাধান এখানে। মনে রাখবেন যে ফলস্বরূপ ডিক্সকে ব্যবহারিক উদ্দেশ্যে অস্পষ্টভাবে পরিবর্তনযোগ্য করার জন্য এটি কিছুটা অতিরিক্ত কাজ করে। অবশ্যই এটি কল করে এখনও এটি চারপাশে হ্যাক করা বেশ সহজ dict.__setitem__(instance, key, value)তবে আমরা সকলেই এখানে প্রাপ্তবয়স্ক।

class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """
    def __key(self):
        return tuple(sorted(self.items()))
    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__,
            ", ".join("{0}={1}".format(
                    str(i[0]),repr(i[1])) for i in self.__key()))

    def __hash__(self):
        return hash(self.__key())
    def __setitem__(self, key, value):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def __delitem__(self, key):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def clear(self):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def pop(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def popitem(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def setdefault(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def update(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

hashdict, অপরিবর্তনীয় হতে হবে কমপক্ষে করার পরে এটিকে হ্যাশ শুরু, তবে কেন ক্যাশে না keyএবং hashগুণাবলীর যেমন মান hashdictবস্তু? আমি পরিবর্তন করেছি __key()এবং __hash__(), এবং এটি আরও দ্রুতগতিতে নিশ্চিত করে পরীক্ষা করেছি। এসও মন্তব্যগুলিতে ফর্ম্যাট কোডটি মঞ্জুরি দেয় না, তাই আমি এটি এখানে লিঙ্ক করব: sam.aiki.info/hashdict.py
স্যাম ওয়াটকিন্স

উত্তর:


71

এখানে হ্যাশযোগ্য অভিধান তৈরির সহজ উপায়। কেবল স্পষ্টত কারণে অন্য অভিধানে এম্বেড করার পরে এগুলি পরিবর্তন না করার কথা মনে রাখবেন।

class hashabledict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

7
এই অকস্মাৎ এর দৃঢ়তা সাহায্য করে না EQ এবং হ্যাশ , যখন আমার আগে উত্তর অনুশীলন পারেন পদ্ধতির মধ্যে __key পদ্ধতি (ব্যবহারের মাধ্যমে করে কাজ করা উচিত যদিও এই এক একটি অপ্রয়োজনীয় itermediate তালিকা করে অপচিত করা যেতে পারে - S / আইটেম দ্বারা নির্ধার্য / iteritems / - পাইথন 2 ধরে ধরে। * যেমন আপনি বলেন না ;-)।
অ্যালেক্স মার্টেলি

4
বাছাইয়ের সাথে টিপলের চেয়ে কেবলমাত্র হিমায়িত ব্যবহার করা ভাল। এটি কেবল দ্রুত হবে না, তবে আপনি ধরে নিতে পারবেন না যে অভিধান কীগুলি তুলনামূলক।
asmeurer

4
দেখে মনে হচ্ছে একটি হ্যাশ ফাংশন এড়ানোর কোনও উপায় থাকতে হবে O(n*log(n))যেখানে এন্ট্রি nসংখ্যা dict। পাইথনের frozensetহ্যাশ ফাংশন লিনিয়ার সময়ে চলে কিনা তা কি কেউ জানেন ?
টম কার্জেস

4
@ হেলো গুডবাই একটি ডিককেও এই dict(key1=value1, key2=value2,...)বা এই জাতীয় তৈরি করা যেতে পারে dict([(key1, value1), (key2, value2),...)])। এটি একই ক্ষেত্রে প্রযোজ্য। আপনার পোস্ট করা সৃষ্টিকে আক্ষরিক
স্মিডো

4
@ স্মিদো: ধন্যবাদ আমিও পাওয়া গেছে যে আপনি শুধু অর্থাত একটি আক্ষরিক নিক্ষেপ করতে পারেন, hashabledict({key_a: val_a, key_b: val_b, ...})
হ্যালো গুডবাই

62

হ্যাশাবলগুলি অপরিবর্তনীয় হওয়া উচিত - এটি প্রয়োগ করে নয় তবে আপনি বিশ্বাস করছেন যে কোনও ডিককে কী হিসাবে প্রথমবার ব্যবহারের পরে তাকে পরিবর্তন করতে না পারে, নিম্নলিখিত পদ্ধতির কাজ করবে:

class hashabledict(dict):
  def __key(self):
    return tuple((k,self[k]) for k in sorted(self))
  def __hash__(self):
    return hash(self.__key())
  def __eq__(self, other):
    return self.__key() == other.__key()

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


ডিক্সটি প্রস্তুত হওয়ার পরে অবশ্যই আমি তা পরিবর্তন করতে চাই না। এটি বাকী প্যাক্র্যাড অ্যালগরিদমকে পৃথক করে দেবে।
সিঙ্গেলাইজেশন নির্মূল 4

তারপরে আমি প্রস্তাবিত সাবক্লাসটি কাজ করবে - নোটটি কীভাবে " পজিশনাল " ইস্যুটিকে বাইপাস করে তা নোট করুন ( আপনি আপনার প্রশ্নটি এটি সম্পাদন করার আগে ;-) এর সাথে sorted__key ;-) সহ লিখেছেন।
অ্যালেক্স মার্টেলি

নেমটুপলের অবস্থান নির্ভর আচরণ হ্যাকটি আমার থেকে বিস্মিত করেছে। সমস্যাটি সমাধানের এটি এখনও সহজতর উপায় হতে পারে ভেবে আমি এটি নিয়ে খেলছিলাম, তবে এটি আমার সমস্ত আশাকে ছিন্ন করেছে (এবং
এটির

ধরা যাক আমার একটি ডিক আছে এবং আমি এটি একটি হ্যাশলডিক্টে ফেলে দিতে চাই। আমি যে কিভাবে করতে হবে?
jononomo

: ধারণা ও ব্যাখ্যা জন্য এই প্রশ্নগুলোর দেখতে @JonCrowell stackoverflow.com/questions/3464061/... , stackoverflow.com/questions/9112300/... , stackoverflow.com/questions/18020074/...
সর্বোচ্চ

32

আপনার উদ্দেশ্যে অভিধানের ব্যবহারযোগ্য করে তোলার জন্য যা প্রয়োজন তা হ'ল একটি __hash__ পদ্ধতি যুক্ত করা:

class Hashabledict(dict):
    def __hash__(self):
        return hash(frozenset(self))

দ্রষ্টব্য, হিমায়িত রূপান্তরটি সমস্ত অভিধানের জন্য কাজ করবে (যেমন এর জন্য কীগুলি বাছাই করার প্রয়োজন হয় না)। তেমনি, অভিধানের মানগুলিতে কোনও বাধা নেই।

যদি অভিন্ন কীগুলির সাথে স্বতন্ত্র মানগুলির সাথে অনেকগুলি অভিধান থাকে তবে হ্যাশগুলির মানগুলিকে বিবেচনায় নেওয়া দরকার। এটি করার দ্রুততম উপায় হ'ল:

class Hashabledict(dict):
    def __hash__(self):
        return hash((frozenset(self), frozenset(self.itervalues())))

এটি frozenset(self.iteritems())দুটি কারণে তুলনায় দ্রুত । প্রথমত, frozenset(self)পদক্ষেপটি অভিধানে সঞ্চিত হ্যাশ মানগুলিকে পুনরায় ব্যবহার করে, এতে অপ্রয়োজনীয় কলগুলি সংরক্ষণ করে hash(key)। দ্বিতীয়ত, আইট্রভ্যালুগুলি ব্যবহার করে মানগুলি সরাসরি অ্যাক্সেস হয়ে যায় এবং প্রতিবার আপনি যখন অনুসন্ধান করবেন তখন মেমরিতে নতুন অনেক কী / মান টিপলস তৈরি করতে আইটেমগুলি ব্যবহার করে অনেকগুলি মেমরি বরাদ্দকারী কলগুলি এড়াতে পারবেন ।


@ রেমন্ড হিট্টিংগার আমি ভুল হলে আমাকে সংশোধন করুন, তবে আমি ভেবেছিলাম যে dictনিজেই এর কীগুলির হ্যাশ মানগুলি ক্যাশে করবে না - যদিও পৃথক শ্রেণি (যেমন str) তাদের হ্যাশগুলি ক্যাশে করতে পছন্দ করে। কমপক্ষে যখন আমি dictকী হিসাবে ব্যবহৃত আমার কাস্টম ক্লাসের উদাহরণগুলি তৈরি করেছি, তখন __hash__প্রতিটি পদ্ধতিতে অ্যাক্সেস অপারেশনে তাদের পদ্ধতিগুলি কল করা হয়েছিল (পাইথন ৩.৪)। আমি সঠিক কিনা বা না, আমি কীভাবে hash(frozenset(self))প্রাক-গণিত হ্যাশ মানগুলি পুনরায় ব্যবহার করতে পারি তা নিশ্চিত নই , যদি না তারা কীগুলির মধ্যে hash(frozenset(self.items())আবদ্ধ থাকে (তবে এই ক্ষেত্রে তাদের পুনরায় ব্যবহারও করবে)।
সর্বাধিক

(কী / মান) টিউপল তৈরি সম্পর্কে আপনার দ্বিতীয় বিষয় হিসাবে, আমি ভেবেছিলাম .items () পদ্ধতিগুলি টিউপসগুলির তালিকার পরিবর্তে একটি ভিউ দেয় এবং এই দৃষ্টিভঙ্গিটি অন্তর্নিহিত কী এবং মানগুলি অনুলিপি করার সাথে জড়িত না। (পাইথন ৩.৪ আবার।) এটি বলেছিল, বেশিরভাগ ইনপুটগুলির আলাদা কী থাকলে কেবল কীগুলি হ্যাশ করার সুবিধাটি আমি দেখতে পাচ্ছি - কারণ (1) হ্যাশ মানগুলির পক্ষে এটি বেশ ব্যয়বহুল, এবং (২) মানগুলি হ্যাশ করার প্রয়োজন হয় এটির জন্য এটি বেশ সীমাবদ্ধ tive
সর্বোচ্চ

6
এটিতে দুটি পৃথক অভিধানের জন্য একই হ্যাশ তৈরির সম্ভাবনাও রয়েছে। বিবেচনা করুন {'one': 1, 'two': 2}এবং{'one': 2, 'two': 1}
এগডুড

মাইক গ্রাহাম তার মন্তব্যে বলেছেন যে অন্য কোনও কারণে ডেরাইভিং ডিক কিন্তু সংজ্ঞা দেওয়া __missing__একটি খারাপ ধারণা। আপনি কি মনে করেন?
পাইওটর ডব্রোগোস্ট

4
পাইথন ২.২ থেকে ডেক থেকে সাবক্লাসিং ভালভাবে সংজ্ঞায়িত করা হয়েছে। সংগ্রহগুলি দেখুন Oআরআর্টডিক্ট এবং সংগ্রহগুলি the পাইথন স্ট্যান্ডার্ড লাইব্রেরির উদাহরণগুলির জন্য কাউন্টার। অন্য মন্তব্যটি ভিত্তিহীন বিশ্বাসের উপর ভিত্তি করে তৈরি করা হয়েছে যে কেবলমাত্র মিউটেবলম্যাপিংয়ের সাবক্লাসগুলি ভালভাবে সংজ্ঞায়িত হয়েছে।
রেমন্ড হেটেঞ্জার

23

প্রদত্ত উত্তরগুলি ঠিক আছে তবে হ্যাশ তৈরির frozenset(...)পরিবর্তে সেগুলি ব্যবহার করে সেগুলি উন্নত করা যেতে পারে tuple(sorted(...)):

>>> import timeit
>>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
4.7758948802947998
>>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
1.8153600692749023

পারফরম্যান্স সুবিধা অভিধানের বিষয়বস্তুর উপর নির্ভর করে, তবে বেশিরভাগ ক্ষেত্রে আমি পরীক্ষা করেছি, হ্যাশিং frozensetকমপক্ষে 2 গুণ বেশি দ্রুত হয় (মূলত এটির জন্য বাছাই করার প্রয়োজন হয় না)।


4
দ্রষ্টব্য, কী এবং মানগুলি উভয়ই অন্তর্ভুক্ত করার দরকার নেই। এই সমাধান হবে অনেক হিসাবে দ্রুত: hash(frozenset(d))
রেমন্ড হেটেঞ্জার

10
@ রেইমন্ড হিট্টিনগার: hash(frozenset(d))একই ধরণের কী তবে বিভিন্ন মান সহ 2 টি ডিক্টের জন্য অভিন্ন হ্যাশগুলির ফলাফল!
ওবেন স্নে

4
এটি কোনও সমস্যা নয়। বিবিধ মানগুলির বিভাজনের মধ্যে পার্থক্য করা __eq__ এর কাজ। __Hash__ এর কাজটি কেবল অনুসন্ধানের জায়গা হ্রাস করা।
রেমন্ড হেটেঞ্জার

4
এটি হ্যাশ এবং ম্যাপিংয়ের তাত্ত্বিক ধারণার জন্য সত্য তবে অভিধান হিসাবে ক্যাশগুলির সাথে অনুসন্ধানের জন্য ব্যবহারিক নয় - অনুরূপ কীগুলি নয় তবে বিভিন্ন মানগুলি একটি মেম-ক্যাশে ফাংশনে পাস করা অস্বাভাবিক নয়। সেক্ষেত্রে ক্যাশে অযৌক্তিকভাবে ম্যাপিংয়ের পরিবর্তে তালিকায় পরিণত হয় যদি কেবল কীগুলি হ্যাশ তৈরির জন্য ব্যবহৃত হয়।
ওবেন সোন

4
ইনডেন্টিকাল কী এবং স্বতন্ত্র মানগুলির সাথে ডিক্টের বিশেষ ক্ষেত্রে আপনার উপর ভিত্তি করে কেবল একটি হ্যাশ সংরক্ষণ করা ভাল frozenset(d.itervalues())। যে ক্ষেত্রে ডাইকের কাছে আলাদা কী রয়েছে, frozenset(d)এটি খুব দ্রুত এবং কীগুলির হ্যাশাবিলিটিতে কোনও বিধিনিষেধ আরোপ করে না। শেষ অবধি, মনে রাখবেন যে ডিক .__ eq__ পদ্ধতি সমান কী / মান জোড়ার জন্য আরও দ্রুত পরীক্ষা করবে যে কোনও কী / মান জোড়ার টিউপলগুলির জন্য হ্যাশকে গুণতে পারে। কী / মান টিপলস ব্যবহার করাও সমস্যাযুক্ত কারণ এটি সমস্ত কীগুলির জন্য সঞ্চিত হ্যাশগুলি ফেলে দেয় (এই কারণেই frozenset(d)এটি এত দ্রুত)।
রেমন্ড হেটেঙ্গার

11

একটি যুক্তিসঙ্গতভাবে পরিষ্কার, সরল বাস্তবায়ন হয়

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

    def __iter__(self):
        return iter(self._d)

    def __len__(self):
        return len(self._d)

    def __getitem__(self, key):
        return self._d[key]

    def __hash__(self):
        return hash(tuple(sorted(self._d.iteritems())))

কেন এটি এত যুক্তিসঙ্গত, পরিষ্কার এবং সোজা? অর্থাত দয়া অন্যান্য উত্তর পার্থক্য, নিতান্ত যেমন ব্যাখ্যা __iter__এবং __len__
কার্ল রিখটার

4
কার্ল রিখটার, আমি কখনও বলিনি যে এটি যুক্তিসঙ্গত, কেবল যুক্তিসঙ্গতভাবে পরিষ্কার। ;)
মাইক গ্রাহাম

@ কার্লাল রিখটার, আমি সংজ্ঞায়িত করেছি __iter__এবং __len__যেহেতু আমার আছে, যেহেতু আমি প্রাপ্ত করছি collections.Mapping; কীভাবে ব্যবহার করবেন collections.Mappingতা সংগ্রহের মডিউল ডকুমেন্টেশনে বেশ ভালভাবে কভার করা হয়েছে । অন্যান্য ব্যক্তিরা যেহেতু ডাইরাইভ করছে তাই প্রয়োজনীয়তা বোধ করে না dictdictঅন্য কোনও কারণে ডাইরিভিং করা কিন্তু সংজ্ঞায়িত __missing__করা একটি খারাপ ধারণা। ডিক স্পেকটি বলছে না যে ডেকে এ জাতীয় ক্ষেত্রে কীভাবে কাজ করে এবং বাস্তবে এটি প্রচুর পরিমাণে অপ্রচলিত পদ্ধতি ব্যবহার করবে যা সাধারণভাবে কম কার্যকর এবং এই বিশেষ ক্ষেত্রে অপ্রাসঙ্গিক আচরণের সাথে অনুসন্ধান সংক্রান্ত পদ্ধতি থাকবে।
মাইক গ্রাহাম 5

7

আমি এই বিষয়টিতে ফিরে আসছি ... এখানে আরও একটি প্রকরণ রয়েছে। আমি dictএকটি __hash__পদ্ধতি যুক্ত করতে সাবক্লাসিংয়ের সাথে অস্বস্তিযুক্ত ; ডিকের যে পরিবর্তন হতে পারে তা থেকে কার্যত কোনও রেহাই পাওয়া যায়নি এবং তারা পরিবর্তিত হবে না এই বিশ্বাস দুর্বল ধারণা বলে মনে হচ্ছে। সুতরাং আমি পরিবর্তে একটি বিল্টিন টাইপের ভিত্তিতে একটি ম্যাপিং তৈরি করার দিকে চেয়েছি যা নিজেই অচল। যদিও tupleএটি একটি সুস্পষ্ট পছন্দ, এতে মানগুলি অ্যাক্সেস করা একটি সাজানো এবং দ্বিখণ্ডিতকে বোঝায়; কোনও সমস্যা নয়, তবে মনে হয় না এটির উপর নির্মিত শক্তিটির অনেক বেশি শক্তি প্রয়োগ করা হচ্ছে।

আপনি কী জ্যাম, একটি মধ্যে জোড় মান frozenset? এটি কী প্রয়োজন, এটি কীভাবে কাজ করবে?

পর্ব 1, আপনার 'আইটেমটির এমনভাবে এনকোড করার একটি উপায় প্রয়োজন যাতে কোনও হিমায়িত তাদের মূলত কীগুলি দ্বারা চিকিত্সা করবে; আমি এটির জন্য একটু সাবক্লাস তৈরি করব।

import collections
class pair(collections.namedtuple('pair_base', 'key value')):
    def __hash__(self):
        return hash((self.key, None))
    def __eq__(self, other):
        if type(self) != type(other):
            return NotImplemented
        return self.key == other.key
    def __repr__(self):
        return repr((self.key, self.value))

কেবলমাত্র এটিই আপনাকে এক অপরিবর্তনীয় ম্যাপিংয়ের দূরত্ব বর্ষণ করতে পারে:

>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])

ডিওহ! দুর্ভাগ্যক্রমে, আপনি যখন সেট অপারেটর ব্যবহার করেন এবং উপাদানগুলি সমান হয় তবে একই বস্তু নয়; যেটির রিটার্ন মান শেষ হবে তা অপরিজ্ঞাত , আমাদের আরও কিছু গিরিশন যেতে হবে through

>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'

যাইহোক, এইভাবে মানগুলি অনুসন্ধান করা জটিল এবং আরও খারাপ, প্রচুর মধ্যবর্তী সেট তৈরি করে; তা করবে না! এর আশেপাশে আমরা একটি 'নকল' কী-মান জুটি তৈরি করব:

class Thief(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(pair(self.key, None))
    def __eq__(self, other):
        self.value = other.value
        return pair(self.key, None) == other

যার ফলস্বরূপ কম সমস্যাযুক্ত:

>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'

এতো গভীর গভীর যাদু; বাকী সমস্ত কিছু এটিকে মোড়ানো হয় যা ডিকের মতো ইন্টারফেস থাকে । যেহেতু আমরা সাবক্লাসিং করছি frozenset, যার একটি খুব ইন্টারফেস রয়েছে, সেখানে প্রচুর পদ্ধতি রয়েছে; আমরা কিছুটা সহায়তা পাই collections.Mappingতবে বেশিরভাগ কাজ হ'ল frozensetসংস্করণগুলির জন্য পদ্ধতিগুলি ওভাররাইড করে যা পরিবর্তে ডিক্টের মতো কাজ করে:

class FrozenDict(frozenset, collections.Mapping):
    def __new__(cls, seq=()):
        return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
    def __getitem__(self, key):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        raise KeyError(key)
    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            return dict(self.iteritems()) == other
        if len(self) != len(other):
            return False
        for key, value in self.iteritems():
            try:
                if value != other[key]:
                    return False
            except KeyError:
                return False
        return True
    def __hash__(self):
        return hash(frozenset(self.iteritems()))
    def get(self, key, default=None):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        return default
    def __iter__(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def iteritems(self):
        for item in frozenset.__iter__(self):
            yield (item.key, item.value)
    def iterkeys(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def itervalues(self):
        for item in frozenset.__iter__(self):
            yield item.value
    def __contains__(self, key):
        return frozenset.__contains__(self, pair(key, None))
    has_key = __contains__
    def __repr__(self):
        return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
    @classmethod
    def fromkeys(cls, keys, value=None):
        return cls((key, value) for key in keys)

যা শেষ পর্যন্ত আমার নিজের প্রশ্নের উত্তর দেয়:

>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5

5

@ অজ্ঞাত দ্বারা গৃহীত উত্তর, পাশাপাশি @ অ্যালেক্সমার্টেলির উত্তর পুরোপুরি সূক্ষ্মভাবে কাজ করে তবে কেবল নিম্নলিখিত সীমাবদ্ধতার অধীনে:

  1. অভিধানের মানগুলি হ্যাশেবল হতে হবে। উদাহরণস্বরূপ, hash(hashabledict({'a':[1,2]}))উত্থাপন করবে TypeError
  2. কীগুলি তুলনা অপারেশনকে অবশ্যই সমর্থন করবে। উদাহরণস্বরূপ, hash(hashabledict({'a':'a', 1:1}))উত্থাপন করবে TypeError
  3. কীগুলিতে তুলনা অপারেটর মোট ক্রম চাপিয়ে দেয়। উদাহরণস্বরূপ, যদি কোনও অভিধানে দুটি কী থাকে frozenset((1,2,3))এবং frozenset((4,5,6))তারা উভয় দিক দিয়ে অসম তুলনা করে। সুতরাং, এই জাতীয় কীগুলির সাহায্যে অভিধানের আইটেমগুলি বাছাইয়ের ফলে একটি স্বেচ্ছাসেবী ক্রম হতে পারে এবং সুতরাং বিধি লঙ্ঘন করবে যে সমান বস্তুর একই হ্যাশ মান থাকতে হবে।

@ ওবোনসন দ্বারা দ্রুততর উত্তরটি 2 এবং 3 এর সীমাবদ্ধতাগুলি সরিয়ে ফেলে, তবে এখনও সীমাবদ্ধতার দ্বারা আবদ্ধ হয় (মানগুলি অবশ্যই প্রাপ্য হতে পারে)।

@ রেমন্ড হিট্টিংগার দ্বারা দ্রুততর উত্তরটি সমস্ত 3 টি সীমাবদ্ধতা সরিয়ে ফেলে কারণ এটি .values()হ্যাশ গণনায় অন্তর্ভুক্ত নয় । তবে, এর পারফরম্যান্সটি তবেই ভাল:

  1. হ্যাশ করা দরকার এমন বেশিরভাগ (অ-সমান) অভিধানগুলির অভিন্ন নয় .keys()

যদি এই শর্তটি সন্তুষ্ট না হয় তবে হ্যাশ ফাংশনটি এখনও বৈধ হবে তবে এতে প্রচুর সংঘর্ষ হতে পারে। উদাহরণস্বরূপ, চূড়ান্ত ক্ষেত্রে যেখানে ওয়েবসাইটের টেমপ্লেট থেকে সমস্ত অভিধান তৈরি করা হয় (কী হিসাবে ক্ষেত্রের নাম, মান হিসাবে ব্যবহারকারী ইনপুট), কীগুলি সর্বদা একই থাকে এবং হ্যাশ ফাংশন সমস্ত ইনপুটগুলির জন্য একই মান প্রদান করে । ফলস্বরূপ, কোনও হ্যাশটেবল যা এই জাতীয় হ্যাশ ফাংশনের উপর নির্ভর করে কোনও আইটেম পুনরুদ্ধার করার O(N)পরিবর্তে তালিকার মতো ধীর হয়ে উঠবে ( পরিবর্তে O(1))।

আমি মনে করি যে উপরে বর্ণিত সমস্ত 4 টি প্রতিবন্ধকতা লঙ্ঘন করা হলেও নীচের সমাধানটি যথাযথভাবে কাজ করবে। এটির অতিরিক্ত সুবিধা রয়েছে যে এটি কেবল অভিধানগুলিই নয়, কোনও পাত্রে এমনকি হ্যাশ করতে পারে, এমনকি তারা পারস্পরিক পরিবর্তনযোগ্য পাত্রে নেস্ট করেছেন।

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

# python 3.4
import collections
import operator
import sys
import itertools
import reprlib

# a wrapper to make an object hashable, while preserving equality
class AutoHash:
    # for each known container type, we can optionally provide a tuple
    # specifying: type, transform, aggregator
    # even immutable types need to be included, since their items
    # may make them unhashable

    # transformation may be used to enforce the desired iteration
    # the result of a transformation must be an iterable
    # default: no change; for dictionaries, we use .items() to see values

    # usually transformation choice only affects efficiency, not correctness

    # aggregator is the function that combines all items into one object
    # default: frozenset; for ordered containers, we can use tuple

    # aggregator choice affects both efficiency and correctness
    # e.g., using a tuple aggregator for a set is incorrect,
    # since identical sets may end up with different hash values
    # frozenset is safe since at worst it just causes more collisions
    # unfortunately, no collections.ABC class is available that helps
    # distinguish ordered from unordered containers
    # so we need to just list them out manually as needed

    type_info = collections.namedtuple(
        'type_info',
        'type transformation aggregator')

    ident = lambda x: x
    # order matters; first match is used to handle a datatype
    known_types = (
        # dict also handles defaultdict
        type_info(dict, lambda d: d.items(), frozenset), 
        # no need to include set and frozenset, since they are fine with defaults
        type_info(collections.OrderedDict, ident, tuple),
        type_info(list, ident, tuple),
        type_info(tuple, ident, tuple),
        type_info(collections.deque, ident, tuple),
        type_info(collections.Iterable, ident, frozenset) # other iterables
    )

    # hash_func can be set to replace the built-in hash function
    # cache can be turned on; if it is, cycles will be detected,
    # otherwise cycles in a data structure will cause failure
    def __init__(self, data, hash_func=hash, cache=False, verbose=False):
        self._data=data
        self.hash_func=hash_func
        self.verbose=verbose
        self.cache=cache
        # cache objects' hashes for performance and to deal with cycles
        if self.cache:
            self.seen={}

    def hash_ex(self, o):
        # note: isinstance(o, Hashable) won't check inner types
        try:
            if self.verbose:
                print(type(o),
                    reprlib.repr(o),
                    self.hash_func(o),
                    file=sys.stderr)
            return self.hash_func(o)
        except TypeError:
            pass

        # we let built-in hash decide if the hash value is worth caching
        # so we don't cache the built-in hash results
        if self.cache and id(o) in self.seen:
            return self.seen[id(o)][0] # found in cache

        # check if o can be handled by decomposing it into components
        for typ, transformation, aggregator in AutoHash.known_types:
            if isinstance(o, typ):
                # another option is:
                # result = reduce(operator.xor, map(_hash_ex, handler(o)))
                # but collisions are more likely with xor than with frozenset
                # e.g. hash_ex([1,2,3,4])==0 with xor

                try:
                    # try to frozenset the actual components, it's faster
                    h = self.hash_func(aggregator(transformation(o)))
                except TypeError:
                    # components not hashable with built-in;
                    # apply our extended hash function to them
                    h = self.hash_func(aggregator(map(self.hash_ex, transformation(o))))
                if self.cache:
                    # storing the object too, otherwise memory location will be reused
                    self.seen[id(o)] = (h, o)
                if self.verbose:
                    print(type(o), reprlib.repr(o), h, file=sys.stderr)
                return h

        raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o)))

    def __hash__(self):
        return self.hash_ex(self._data)

    def __eq__(self, other):
        # short circuit to save time
        if self is other:
            return True

        # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first
        # 2) any other situation => lhs.__eq__ will be called first

        # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either
        # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data
        # case 2. neither side is a subclass of the other; self is lhs
        # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented
        # case 3. neither side is a subclass of the other; self is rhs
        # => we can't compare to another type, and the other side already tried and failed;
        # we should return False, but NotImplemented will have the same effect
        # any other case: we won't reach the __eq__ code in this class, no need to worry about it

        if isinstance(self, type(other)): # identifies case 1
            return self._data == other._data
        else: # identifies cases 2 and 3
            return NotImplemented

d1 = {'a':[1,2], 2:{3:4}}
print(hash(AutoHash(d1, cache=True, verbose=True)))

d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True)
print(hash(d))

2

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

def __setstate__(self, objstate):
    for k,v in objstate.items():
        dict.__setitem__(self,k,v)
def __reduce__(self):
    return (hashdict, (), dict(self),)

-2

যদি আপনি অভিধানে নম্বর না রাখেন এবং আপনার অভিধানগুলি সহ ভেরিয়েবলগুলি কখনই হারাবেন না, আপনি এটি করতে পারেন:

cache[id(rule)] = "whatever"

যেহেতু আইডি () প্রতিটি অভিধানের জন্য স্বতন্ত্র

সম্পাদনা:

ওহ দুঃখিত, হ্যাঁ এই ক্ষেত্রে অন্যান্য লোকেরা যা বলেছিল তা আরও ভাল। আমি মনে করি আপনি আপনার অভিধানগুলি একটি স্ট্রিং, যেমন পছন্দ করতে পারেন serial

cache[ 'foo:bar' ] = 'baz'

যদি আপনার কীগুলি থেকে আপনার অভিধানগুলি পুনরুদ্ধার করতে হয় তবে আপনার কিছু পছন্দসই কাজ করতে হবে

cache[ 'foo:bar' ] = ( {'foo':'bar'}, 'baz' )

আমার মনে হয় এর সুবিধাটি হ'ল আপনাকে এত কোড লিখতে হবে না।


হুঁ, না; এটি আমি যা খুঁজছি তা নয় cache[id({'foo':'bar'})] = 'baz'; id({'foo':'bar'}) not in cache:, আমি যখন প্রথম স্থানে চাবি হিসাবে ডিক্ট ব্যবহার করতে চাই তখন গতিশীলভাবে কী তৈরি করতে সক্ষম হওয়া গুরুত্বপূর্ণ।
সিঙ্গেলাইজেশন ইলিমিনেশন

4
ডিক্টগুলি সিরিয়াল করা ঠিক আছে, সেগুলি ক্রমিক করার জন্য আপনার কাছে কি কোনও ক্ষতিপূরণ রয়েছে? এটাই আমি খুঁজছি
সিঙ্গেলাইজেশন এলোমিনেশন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.