Есть ли простой способ найти ключ, зная значение в словаре?
Все, что я могу придумать, это:
key = [key for key, value in dict_obj.items() if value == 'value'][0]
Есть ли простой способ найти ключ, зная значение в словаре?
Все, что я могу придумать, это:
key = [key for key, value in dict_obj.items() if value == 'value'][0]
Нет. Не забывайте, что значение может быть найдено на любом количестве ключей, включая 0 или более 1.
Ваше понимание списка проходит через все элементы dict, которые находят все совпадения, а затем просто возвращает первый ключ. Это выражение генератора будет перебирать только по мере необходимости, чтобы вернуть первое значение:
key = next(key for key, value in dd.items() if value == 'value')
где dd
- это dict. Поднимет StopIteration
, если совпадение не найдено, поэтому вы можете поймать его и вернуть более подходящее исключение, например ValueError
или KeyError
.
Бывают случаи, когда словарь является одним: одним отображением
Например,
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())
Эта версия на 26% короче вашего, но работает тождественно, даже для избыточных/неоднозначных значений (возвращает первое совпадение, как и ваше). Однако он, вероятно, в два раза медленнее вашего, потому что он дважды создает список из dict.
key = dict_obj.keys()[dict_obj.values().index(value)]
Или, если вы предпочитаете краткость по чтению, вы можете сохранить еще один символ с помощью
key = list(dict_obj)[dict_obj.values().index(value)]
И если вы предпочитаете эффективность, лучше использовать @PaulMcGuire . Если есть много ключей, которые имеют одно и то же значение, более эффективно не создавать экземпляр этого списка ключей со списком и вместо этого использовать генератор:
key = (key for key, value in dict_obj.items() if value == 'value').next()
Может быть, подобный словарю класс, такой как DoubleDict
ниже, что вы хотите? Вы можете использовать любой из предоставленных метаклассов в сочетании с DoubleDict
или вообще не использовать какой-либо метакласс.
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))
Так как это все еще очень актуально, первый хит Google, и я просто потрачу некоторое время на это, я опубликую свое (работающее в 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'
Он даст вам первое значение, которое соответствует.
Существует не один, насколько я знаю, но один из способов сделать это - создать диктовку для нормального поиска по ключу и другой dict для обратного поиска по значению.
Вот пример такой реализации здесь:
http://code.activestate.com/recipes/415903-two-dict-classes-which-can-lookup-keys-by-value-an/
Это означает, что поиск ключей для значения может привести к нескольким результатам, которые могут быть возвращены в виде простого списка.
Нет, вы не можете сделать это эффективно, не глядя во все ключи и не проверяя все свои значения. Для этого вам понадобится 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])
}
Сквозные значения в словаре могут быть объектами любого типа, которые они не могут хэшировать или индексировать другим способом. Поэтому поиск ключа по значению является неестественным для этого типа коллекции. Любой такой запрос может быть выполнен только в O (n) времени. Поэтому, если это частая задача, вы должны взглянуть на некоторую индексацию ключа, например, Jon sujjested или, возможно, даже некоторый пространственный индекс (DB или http://pypi.python.org/pypi/Rtree/).
Я знаю, что это может считаться "расточительным", но в этом случае я часто храню ключ в качестве дополнительного столбца в записи значения:
d = {'key1' : ('key1', val, val...), 'key2' : ('key2', val, val...) }
это компромисс и кажется неправильным, но он прост и работает и, конечно же, зависит от значений, а не простых значений, а кортежей.
Я использую словари как своего рода "базу данных", поэтому мне нужно найти ключ, который я могу повторно использовать. Для моего случая, если значение ключа None
, тогда я могу его использовать и повторно использовать без необходимости "выделять" другой идентификатор. Просто подумал, что я поделюсь им.
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
здесь...
Поскольку значение может отсутствовать в dict, более пифонический и автоматически документированный код будет:
a # Value to search against
x = None # Searched key
for k, v in d.items():
if v == a:
x = k
break
x # Now contains the key or None if not found.
Действительно, dicts не принимаются, чтобы ответить на такую проблематику, если вы столкнулись с этой проблемой в новой разработанной программе, тогда вам, вероятно, следует рассмотреть ваш дизайн.
key in dict.values()
Это буквально это