การค้นหาพจนานุกรมผกผันใน Python


103

มีวิธีที่ตรงไปตรงมาในการค้นหาคีย์โดยการรู้ค่าในพจนานุกรมหรือไม่?

ทั้งหมดที่ฉันคิดได้คือ:

key = [key for key, value in dict_obj.items() if value == 'value'][0]

สามารถทำซ้ำได้: stackoverflow.com/questions/483666/…
Tobias Kienzler


Google แนะนำฉันที่นี่ ... และฉันต้องบอกว่า .. ทำไมไม่มีใครใช้iteritemsสำหรับฉันสิ่งนี้ทำให้ความแตกต่างเร็วขึ้น 40 เท่า ... โดยใช้วิธี () .next
Angry 84

4
หากคุณมีการค้นหาแบบย้อนกลับจำนวนมากที่ต้องทำ:reverse_dictionary = {v:k for k,v in dictionary.items()}
Austin

คำตอบ:


4

ไม่มีเลย อย่าลืมว่าค่านี้อาจพบได้ในจำนวนคีย์ใด ๆ รวมทั้ง 0 หรือมากกว่า 1


2
python มีเมธอด .index ในรายการผลตอบแทนดัชนีที่พบครั้งแรกพร้อมค่าที่ระบุหรือข้อยกเว้นหากไม่พบ ... สาเหตุใดที่ไม่สามารถใช้ความหมายดังกล่าวกับพจนานุกรมได้?
Brian Jack

@BrianJack: พจนานุกรมไม่ได้เรียงลำดับเหมือนชุด ดู collections.OrderedDict สำหรับการใช้งานที่มีการสั่งซื้อ
Martijn Pieters

3
.index ต้องการเพียงเพื่อรับประกันว่าจะส่งคืนค่าเดียวและไม่จำเป็นต้องเป็นคำศัพท์ก่อนเพียงว่าเป็นการจับคู่ครั้งแรกและลักษณะการทำงานจะคงที่ (การเรียกใช้คำสั่งเดียวกันหลายครั้งในช่วงเวลาหนึ่งควรให้องค์ประกอบที่ตรงกัน) เว้นแต่พจนานุกรมจะจัดเรียงแฮชที่ไม่ได้แก้ไขใหม่เมื่อเวลาผ่านไปเนื่องจากองค์ประกอบอื่น ๆ ได้รับการเพิ่มลบหรือแก้ไขก็จะยังคงทำงานได้อย่างเหมาะสม การใช้งานที่ไร้เดียงสา: dictObject.items () ดัชนี (คีย์)
Brian Jack

ประเด็นหลักของ. index () คือโดยความหมายเราไม่สนใจเกี่ยวกับรายการที่ซ้ำกันเพียง แต่เราอาจค้นหาองค์ประกอบเดียวอย่างสม่ำเสมอ
Brian Jack

131
ฉันเกลียดคำตอบที่ไม่ใช่คำตอบแบบนี้ "หยุดพยายามทำในสิ่งที่คุณอยากทำอย่างมีเหตุผล!" คือไม่ได้คำตอบที่ได้รับการยอมรับ เหตุใดจึงได้รับการยอมรับ ในฐานะที่เป็นคำตอบที่ได้รับคะแนนสูงกว่าสำหรับคำถามนี้การค้นหาพจนานุกรมแบบย้อนกลับสามารถนำไปใช้งานได้เล็กน้อยโดยใช้ Pure-Python น้อยกว่า 80 ตัว มันไม่ได้ "ตรงไปตรงมา" มากไปกว่านั้น โซลูชันของPaul McGuireอาจมีประสิทธิภาพมากที่สุด แต่ก็ใช้ได้ผลทั้งหมด </sigh>
Cecil Curry

96

ความเข้าใจในรายการของคุณจะตรวจสอบรายการทั้งหมดของ dict เพื่อค้นหารายการที่ตรงกันทั้งหมดจากนั้นส่งคืนคีย์แรก นิพจน์ตัวสร้างนี้จะวนซ้ำเท่าที่จำเป็นเพื่อส่งคืนค่าแรก:

key = next(key for key, value in dd.items() if value == 'value')

ที่ddเป็นบริการพจนานุกรม จะยกStopIterationถ้าการแข่งขันไม่พบดังนั้นคุณอาจต้องการที่จะจับที่และกลับมาเป็นข้อยกเว้นที่เหมาะสมมากขึ้นเช่นหรือValueErrorKeyError


1
ใช่มันควรจะเพิ่มข้อยกเว้นเช่นเดียวกับ listObject.index (คีย์) เมื่อคีย์ไม่อยู่ในรายการ
Brian Jack

7
นอกจากนี้ยังkeys = { key for key,value in dd.items() if value=='value' }ได้รับชุดของคีย์ทั้งหมดหากมีการแข่งขันหลายรายการ
askewchan

6
@askewchan - ไม่จำเป็นต้องส่งคืนสิ่งนี้เป็นชุดจริงๆคีย์ dict ต้องไม่ซ้ำกันอยู่แล้วเพียงแค่ส่งคืนรายการหรือดีกว่าส่งคืนนิพจน์ตัวสร้างและปล่อยให้ผู้โทรใส่ในคอนเทนเนอร์ที่ต้องการ
PaulMcG

57

มีหลายกรณีที่พจนานุกรมเป็นแบบหนึ่ง: หนึ่งการแมป

เช่น,

d = {1: "one", 2: "two" ...}

แนวทางของคุณใช้ได้ถ้าคุณทำการค้นหาเพียงครั้งเดียว อย่างไรก็ตามหากคุณจำเป็นต้องทำการค้นหามากกว่าหนึ่งรายการการสร้างพจนานุกรมผกผันจะมีประสิทธิภาพมากกว่า

ivd = {v: k for k, v in d.items()}

หากมีความเป็นไปได้ที่จะมีหลายคีย์ที่มีค่าเดียวกันคุณจะต้องระบุลักษณะการทำงานที่ต้องการในกรณีนี้

หาก Python ของคุณมีอายุ 2.6 หรือเก่ากว่าคุณสามารถใช้ไฟล์

ivd = dict((v, k) for k, v in d.items())

6
การเพิ่มประสิทธิภาพที่ดี แต่ฉันคิดว่าคุณตั้งใจจะเปลี่ยนรายการ 2-tuples ของคุณให้เป็นพจนานุกรมโดยใช้ dict ():ivd=dict([(v,k) for (k,v) in d.items()])
hobs

4
@hobs เพียงแค่ใช้การเข้าใจคำสั่งแทนการเข้าใจรายการ:invd = { v:k for k,v in d.items() }
askewchan

ความเข้าใจ @gnibbler dict ยังไม่ถูกย้ายกลับไปที่ Python 2.6 ดังนั้นหากคุณต้องการพกพาคุณจะต้องใส่อักขระพิเศษ 6 ตัวสำหรับ dict () รอบ ๆ เครื่องกำเนิดไฟฟ้าของ 2-tuples หรือความเข้าใจรายการ 2
hobs

@hobs ฉันเพิ่มสิ่งนั้นในคำตอบของฉัน
John La Rooy

32

เวอร์ชันนี้สั้นกว่าของคุณ 26% แต่ทำงานเหมือนกันแม้จะเป็นค่าที่ซ้ำซ้อน / ไม่ชัดเจน (ส่งคืนการจับคู่ครั้งแรกตามที่คุณทำ) อย่างไรก็ตามมันอาจจะช้ากว่าของคุณถึงสองเท่าเพราะมันสร้างรายการจาก dict สองครั้ง

key = dict_obj.keys()[dict_obj.values().index(value)]

หรือหากคุณต้องการความกะทัดรัดมากกว่าความสามารถในการอ่านคุณสามารถบันทึกอักขระได้อีกหนึ่งตัวด้วย

key = list(dict_obj)[dict_obj.values().index(value)]

และถ้าคุณต้องการประสิทธิภาพของ @ PaulMcGuire's แนวทางจะดีกว่า หากมีคีย์จำนวนมากที่ใช้ค่าเดียวกันจะมีประสิทธิภาพมากกว่าที่จะไม่สร้างอินสแตนซ์รายการคีย์นั้นด้วยความเข้าใจรายการและใช้ตัวสร้างแทน:

key = (key for key, value in dict_obj.items() if value == 'value').next()

2
สมมติว่าเป็นการทำงานของอะตอมคีย์และค่ารับประกันว่าจะอยู่ในลำดับเดียวกันหรือไม่?
Noctis Skytower

1
@NoctisSkytower ใช่dict.keys()และdict.values()รับประกันว่าจะสอดคล้องกันตราบเท่าที่dictไม่มีการกลายพันธุ์ระหว่างการโทร
เตา

7

เนื่องจากสิ่งนี้ยังคงมีความเกี่ยวข้องมาก Google hit แรกและฉันใช้เวลาพอสมควรในการหาสิ่งนี้ฉันจะโพสต์โซลูชัน (ทำงานใน Python 3) ของฉัน:

testdict = {'one'   : '1',
            'two'   : '2',
            'three' : '3',
            'four'  : '4'
            }

value = '2'

[key for key in testdict.items() if key[1] == value][0][0]

Out[1]: 'two'

จะให้ค่าแรกที่ตรงกัน


6

บางทีคลาสที่เหมือนพจนานุกรมเช่นDoubleDictด้านล่างคือสิ่งที่คุณต้องการ? คุณสามารถใช้ metaclasses ใดก็ได้ที่ให้มาในการเชื่อมต่อกับDoubleDictหรืออาจหลีกเลี่ยงการใช้ metaclass ใด ๆ เลยก็ได้

import functools
import threading

################################################################################

class _DDChecker(type):

    def __new__(cls, name, bases, classdict):
        for key, value in classdict.items():
            if key not in {'__new__', '__slots__', '_DoubleDict__dict_view'}:
                classdict[key] = cls._wrap(value)
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def check(self, *args, **kwargs):
            value = function(self, *args, **kwargs)
            if self._DoubleDict__forward != \
               dict(map(reversed, self._DoubleDict__reverse.items())):
                raise RuntimeError('Forward & Reverse are not equivalent!')
            return value
        return check

################################################################################

class _DDAtomic(_DDChecker):

    def __new__(cls, name, bases, classdict):
        if not bases:
            classdict['__slots__'] += ('_DDAtomic__mutex',)
            classdict['__new__'] = cls._atomic_new
        return super().__new__(cls, name, bases, classdict)

    @staticmethod
    def _atomic_new(cls, iterable=(), **pairs):
        instance = object.__new__(cls, iterable, **pairs)
        instance.__mutex = threading.RLock()
        instance.clear()
        return instance

    @staticmethod
    def _wrap(function):
        @functools.wraps(function)
        def atomic(self, *args, **kwargs):
            with self.__mutex:
                return function(self, *args, **kwargs)
        return atomic

################################################################################

class _DDAtomicChecker(_DDAtomic):

    @staticmethod
    def _wrap(function):
        return _DDAtomic._wrap(_DDChecker._wrap(function))

################################################################################

class DoubleDict(metaclass=_DDAtomicChecker):

    __slots__ = '__forward', '__reverse'

    def __new__(cls, iterable=(), **pairs):
        instance = super().__new__(cls, iterable, **pairs)
        instance.clear()
        return instance

    def __init__(self, iterable=(), **pairs):
        self.update(iterable, **pairs)

    ########################################################################

    def __repr__(self):
        return repr(self.__forward)

    def __lt__(self, other):
        return self.__forward < other

    def __le__(self, other):
        return self.__forward <= other

    def __eq__(self, other):
        return self.__forward == other

    def __ne__(self, other):
        return self.__forward != other

    def __gt__(self, other):
        return self.__forward > other

    def __ge__(self, other):
        return self.__forward >= other

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

    def __getitem__(self, key):
        if key in self:
            return self.__forward[key]
        return self.__missing_key(key)

    def __setitem__(self, key, value):
        if self.in_values(value):
            del self[self.get_key(value)]
        self.__set_key_value(key, value)
        return value

    def __delitem__(self, key):
        self.pop(key)

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

    def __contains__(self, key):
        return key in self.__forward

    ########################################################################

    def clear(self):
        self.__forward = {}
        self.__reverse = {}

    def copy(self):
        return self.__class__(self.items())

    def del_value(self, value):
        self.pop_key(value)

    def get(self, key, default=None):
        return self[key] if key in self else default

    def get_key(self, value):
        if self.in_values(value):
            return self.__reverse[value]
        return self.__missing_value(value)

    def get_key_default(self, value, default=None):
        return self.get_key(value) if self.in_values(value) else default

    def in_values(self, value):
        return value in self.__reverse

    def items(self):
        return self.__dict_view('items', ((key, self[key]) for key in self))

    def iter_values(self):
        return iter(self.__reverse)

    def keys(self):
        return self.__dict_view('keys', self.__forward)

    def pop(self, key, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if key in self:
            value = self[key]
            self.__del_key_value(key, value)
            return value
        if default:
            return default[0]
        raise KeyError(key)

    def pop_key(self, value, *default):
        if len(default) > 1:
            raise TypeError('too many arguments')
        if self.in_values(value):
            key = self.get_key(value)
            self.__del_key_value(key, value)
            return key
        if default:
            return default[0]
        raise KeyError(value)

    def popitem(self):
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError('popitem(): dictionary is empty')
        return key, self.pop(key)

    def set_key(self, value, key):
        if key in self:
            self.del_value(self[key])
        self.__set_key_value(key, value)
        return key

    def setdefault(self, key, default=None):
        if key not in self:
            self[key] = default
        return self[key]

    def setdefault_key(self, value, default=None):
        if not self.in_values(value):
            self.set_key(value, default)
        return self.get_key(value)

    def update(self, iterable=(), **pairs):
        for key, value in (((key, iterable[key]) for key in iterable.keys())
                           if hasattr(iterable, 'keys') else iterable):
            self[key] = value
        for key, value in pairs.items():
            self[key] = value

    def values(self):
        return self.__dict_view('values', self.__reverse)

    ########################################################################

    def __missing_key(self, key):
        if hasattr(self.__class__, '__missing__'):
            return self.__missing__(key)
        if not hasattr(self, 'default_factory') \
           or self.default_factory is None:
            raise KeyError(key)
        return self.__setitem__(key, self.default_factory())

    def __missing_value(self, value):
        if hasattr(self.__class__, '__missing_value__'):
            return self.__missing_value__(value)
        if not hasattr(self, 'default_key_factory') \
           or self.default_key_factory is None:
            raise KeyError(value)
        return self.set_key(value, self.default_key_factory())

    def __set_key_value(self, key, value):
        self.__forward[key] = value
        self.__reverse[value] = key

    def __del_key_value(self, key, value):
        del self.__forward[key]
        del self.__reverse[value]

    ########################################################################

    class __dict_view(frozenset):

        __slots__ = '__name'

        def __new__(cls, name, iterable=()):
            instance = super().__new__(cls, iterable)
            instance.__name = name
            return instance

        def __repr__(self):
            return 'dict_{}({})'.format(self.__name, list(self))

4

ไม่ได้คุณไม่สามารถทำได้อย่างมีประสิทธิภาพโดยไม่ต้องดูคีย์ทั้งหมดและตรวจสอบค่าทั้งหมด ดังนั้นคุณจะต้องใช้O(n)เวลาในการทำสิ่งนี้ หากคุณต้องการค้นหาจำนวนมากคุณจะต้องดำเนินการนี้อย่างมีประสิทธิภาพโดยการสร้างพจนานุกรมแบบย้อนกลับ (สามารถทำได้ในO(n)) จากนั้นทำการค้นหาภายในพจนานุกรมที่กลับรายการนี้ (การค้นหาแต่ละครั้งจะใช้เวลาโดยเฉลี่ยO(1) )

นี่คือตัวอย่างของวิธีการสร้างพจนานุกรมแบบย้อนกลับ (ซึ่งจะสามารถทำการแมปหนึ่งถึงหลาย ๆ รายการได้) จากพจนานุกรมปกติ:

for i in h_normal:
    for j in h_normal[i]:
        if j not in h_reversed:
            h_reversed[j] = set([i])
        else:
            h_reversed[j].add(i)

ตัวอย่างเช่นหากไฟล์

h_normal = {
  1: set([3]), 
  2: set([5, 7]), 
  3: set([]), 
  4: set([7]), 
  5: set([1, 4]), 
  6: set([1, 7]), 
  7: set([1]), 
  8: set([2, 5, 6])
}

คุณh_reversedจะเป็น

{
  1: set([5, 6, 7]),
  2: set([8]), 
  3: set([1]), 
  4: set([5]), 
  5: set([8, 2]), 
  6: set([8]), 
  7: set([2, 4, 6])
}

2

ไม่มีวิธีใดเท่าที่ฉันรู้วิธีหนึ่งที่ทำได้คือการสร้างคำสั่งสำหรับการค้นหาตามปกติโดยใช้คีย์และอีกคำสั่งหนึ่งสำหรับการค้นหาแบบย้อนกลับตามค่า

มีตัวอย่างของการใช้งานดังกล่าวที่นี่:

http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/

นี่หมายความว่าการค้นหาคีย์สำหรับค่าอาจทำให้ได้ผลลัพธ์หลายรายการซึ่งสามารถส่งคืนเป็นรายการแบบง่าย


โปรดทราบว่ามีหลายค่าที่เป็นไปได้มากมายที่ไม่ใช่คีย์ที่ถูกต้อง
Ignacio Vazquez-Abrams

1

ฉันรู้ว่านี่อาจถือเป็น 'สิ้นเปลือง' แต่ในสถานการณ์นี้ฉันมักจะเก็บคีย์ไว้เป็นคอลัมน์เพิ่มเติมในเรกคอร์ดค่า:

d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) }

มันเป็นการแลกเปลี่ยนและรู้สึกผิด แต่มันง่ายและใช้งานได้จริงและแน่นอนว่าขึ้นอยู่กับค่าที่เป็นสิ่งที่เพิ่มขึ้นมากกว่าค่าง่ายๆ


1

ทำพจนานุกรมย้อนกลับ

reverse_dictionary = {v:k for k,v in dictionary.items()} 

หากคุณมีการค้นหาแบบย้อนกลับจำนวนมากที่ต้องทำ


สิ่งนี้ใช้ได้เฉพาะเมื่อมีการแมประหว่างคีย์และค่าแบบ 1: 1
Noel Yap


0

ด้วยค่าในพจนานุกรมอาจเป็นวัตถุประเภทใดก็ได้ที่ไม่สามารถแฮชหรือจัดทำดัชนีด้วยวิธีอื่นได้ ดังนั้นการค้นหาคีย์ด้วยค่าจึงไม่เป็นธรรมชาติสำหรับคอลเล็กชันประเภทนี้ แบบสอบถามใด ๆ เช่นนั้นสามารถดำเนินการได้ในเวลา O (n) เท่านั้น ดังนั้นหากเป็นงานบ่อยคุณควรมองหาการสร้างดัชนีของคีย์เช่น Jon sujjested หรือแม้แต่ดัชนีเชิงพื้นที่ (DB หรือhttp://pypi.python.org/pypi/Rtree/ )


-1

ฉันใช้พจนานุกรมเป็น "ฐานข้อมูล" ประเภทหนึ่งดังนั้นฉันจึงต้องหาคีย์ที่สามารถนำมาใช้ใหม่ได้ สำหรับกรณีของฉันถ้าค่าของคีย์คือNoneฉันสามารถนำไปใช้ซ้ำได้โดยไม่ต้อง "จัดสรร" id อื่น แค่คิดว่าฉันจะแบ่งปัน

db = {0:[], 1:[], ..., 5:None, 11:None, 19:[], ...}

keys_to_reallocate = [None]
allocate.extend(i for i in db.iterkeys() if db[i] is None)
free_id = keys_to_reallocate[-1]

ฉันเช่นนี้เพราะผมไม่ต้องพยายามและจับข้อผิดพลาดใด ๆ เช่นหรือStopIteration IndexErrorหากมีคีย์พร้อมใช้งานก็free_idจะมีคีย์ Noneถ้ามีไม่ได้แล้วมันก็จะเป็น อาจจะไม่ใช่ pythonic แต่ฉันไม่อยากใช้tryที่นี่ ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.