Получить ключ по значению в словаре


632

Я сделал функцию, которая будет искать возраст в a Dictionaryи показывать подходящее имя:

dictionary = {'george' : 16, 'amber' : 19}
search_age = raw_input("Provide age")
for age in dictionary.values():
    if age == search_age:
        name = dictionary[age]
        print name

Я знаю, как сравнить и найти возраст, я просто не знаю, как показать имя человека. Кроме того, я получаю KeyErrorиз-за строки 5. Я знаю, что это не правильно, но я не могу понять, как заставить его искать в обратном направлении.



Вы найдете слово на основе его определения в словаре? НЕТ.
Джосси Кальдерон

Ответы:


563

Здесь ничего нет. dictне предназначен для использования таким образом.

dictionary = {'george': 16, 'amber': 19}
search_age = input("Provide age")
for name, age in dictionary.items():  # for name, age in dictionary.iteritems():  (for Python 2.x)
    if age == search_age:
        print(name)

137
В Python 3.x list.items()вместо list.iteritems()должен быть использован
Юрий Петровский

63
Я не согласен ... Ответ agf ниже более конструктивен. Совершенно разумный вариант использования не является «непреднамеренным» (в любом случае, понимание списка подходит для такого варианта использования). А dictможет быть для нескольких вещей в разное время; ключи и значения имеют четкое значение, конечно, но « dictэлементы с данным значением» - вполне разумный запрос. Рекомендация использовать список пар не учитывает контекст, согласно которому один элемент является « определением » другого, например, в списках параметров ...
Луи Мэддокс,

1
Я не согласен с этим ответом. Тот факт, что это возможно, как показано в ответе Стенио Элсона, не означает, что он не предназначен для использования в качестве такового. Совсем не полезно.
Tropicalrambler

Вы найдете слово в словаре на основе его определения? НЕТ. @Tropicalrambler
Джосси Кальдерон

Хотя у вас есть точка зрения, что стандартное использование словаря слов состоит в поиске определения слова со словом = ключ и определением = значение, современные языки программирования позволяют при необходимости искать по значению. Если вы работаете с объектом пары ключ: значение (назовите его словарь, кортеж, любое имя для любого языка). В Python это факт, что вы все еще можете индексировать значения структуры, чтобы найти соответствующие ключи.
Tropicalrambler

599
mydict = {'george': 16, 'amber': 19}
print mydict.keys()[mydict.values().index(16)]  # Prints george

Или в Python 3.x:

mydict = {'george': 16, 'amber': 19}
print(list(mydict.keys())[list(mydict.values()).index(16)])  # Prints george

По сути, он разделяет значения словаря в списке, находит позицию имеющегося у вас значения и получает ключ в этой позиции.

Подробнее о keys()и .values()в Python 3: Как я могу получить список значений из Dict?


23
Выглядит отлично, но работает ли он всегда? Я имею в виду, list.keys()и list.values()функции генерируют элементы в том же порядке?
iskorum

17
Да, они гарантированно будут последовательными. Кроме того, порядок гарантированно не изменится итерациями, пока словарь не изменяется.
Veedrac

9
Это выглядит хорошим решением, но индекс дал только одно правильное значение, так что если вы имеете несколько равных значений, то он должен вернуть несколько ключей, верно?
Джеймс Сапам

12
@ArtOfWarfare docs.python.org/3/library/stdtypes.html#dict-views : «Если представления ключей, значений и элементов перебираются без внесения изменений в словарь, порядок элементов будет напрямую соответствовать».
Veedrac

5
@sinekonata: он все еще выполняет дорогую петлю под капотом; цикл просто скрыт внутри indexметода.
user2357112 поддерживает Monica

252

Если вам нужно и имя, и возраст, вы должны использовать, .items()который дает вам ключевые (key, value)кортежи:

for name, age in mydict.items():
    if age == search_age:
        print name

Вы можете распаковать кортеж в две отдельные переменные прямо в forцикле, а затем сопоставить возраст.

Вам также следует рассмотреть вопрос об изменении словаря, если вы, как правило, будете искать по возрасту, и нет двух людей одного возраста:

{16: 'george', 19: 'amber'}

так что вы можете найти имя для возраста, просто делая

mydict[search_age]

Я назвал его mydictвместо, listпотому что listэто имя встроенного типа, и вы не должны использовать это имя для чего-либо еще.

Вы даже можете получить список всех людей данного возраста в одной строке:

[name for name, age in mydict.items() if age == search_age]

или если в каждом возрасте есть только один человек:

next((name for name, age in mydict.items() if age == search_age), None)

который просто даст вам, Noneесли нет никого с таким возрастом.

Наконец, если dictон длинный, и вы работаете на Python 2, вам следует подумать об использовании .iteritems()вместо того, .items()как это сделал Cat Plus Plus в своем ответе, поскольку для этого не нужно делать копию списка.


9
Правильно, но если вы собираетесь выполнять линейный поиск, вы можете также заменить dictсписок пар.
Фред Фу

9
Если ваше обычное действие не выглядит возрастом по имени, в этом случае dictимеет смысл.
AGF

2
Кажется странным предположить, что существует только один человек с каждым возрастом, в то время как с другой стороны, для каждого человека вполне логично иметь один возраст.
Даннид

@Dannid Да, но проблема может быть легко обобщена. Например, у вас может быть таблица поиска с уникальными ключами и соответствующими им уникальными значениями. Затем вы можете посмотреть на вещи симметрично value --> keyилиkey --> value
pfabri

68

Я подумал, что было бы интересно указать, какие методы являются самыми быстрыми и в каком сценарии:

Вот несколько тестов, которые я провел (на MacBook Pro 2012 года)

>>> def method1(list,search_age):
...     for name,age in list.iteritems():
...             if age == search_age:
...                     return name
... 
>>> def method2(list,search_age):
...     return [name for name,age in list.iteritems() if age == search_age]
... 
>>> def method3(list,search_age):
...     return list.keys()[list.values().index(search_age)]

Результаты profile.run()по каждому методу 100000 раз:

Способ 1:

>>> profile.run("for i in range(0,100000): method1(list,16)")
     200004 function calls in 1.173 seconds

Способ 2:

>>> profile.run("for i in range(0,100000): method2(list,16)")
     200004 function calls in 1.222 seconds

Способ 3:

>>> profile.run("for i in range(0,100000): method3(list,16)")
     400004 function calls in 2.125 seconds

Таким образом, это показывает, что для небольшого требования метод 1 является самым быстрым. Это наиболее вероятно, потому что он возвращает первое совпадение, в отличие от всех совпадений, таких как метод 2 (см. Примечание ниже).


Интересно, что, выполняя те же самые тесты на моем дикте с 2700 записями, я получаю совершенно разные результаты (на этот раз - 10000 раз):

Способ 1:

>>> profile.run("for i in range(0,10000): method1(UIC_CRS,'7088380')")
     20004 function calls in 2.928 seconds

Способ 2:

>>> profile.run("for i in range(0,10000): method2(UIC_CRS,'7088380')")
     20004 function calls in 3.872 seconds

Способ 3:

>>> profile.run("for i in range(0,10000): method3(UIC_CRS,'7088380')")
     40004 function calls in 1.176 seconds

Итак, метод 3 намного быстрее. Показывает, что размер вашего выбора влияет на то, какой метод вы выберете.

Примечания: Метод 2 возвращает список всех имен, тогда как методы 1 и 3 возвращают только первое совпадение. Я не рассматривал использование памяти. Я не уверен, что метод 3 создает 2 дополнительных списка (keys () и values ​​()) и сохраняет их в памяти.


6
Просто обновление: кажется, что dict.values ​​() и dict.keys () возвращают списки, которые ссылаются на объекты из исходного dict, поэтому метод 3 также использует наименьшее количество памяти (он создает только два объекта тонкого списка которые обертывают содержимое диктовок, тогда как остальные создают элементы итератора
Патрик

Я просто хотел сравнить это сам с прокруткой вниз, бац, вот оно. Спасибо! Технически, как вы уже указали, метод 2 не делает то же самое, что 1 и 3, потому что он возвращает все совпадения. было бы неплохо увидеть результаты, например, return next ([..]).
BluBb_mADe

Еще одно важное замечание - это версия Python. Я знаю, что некоторые версии имеют более эффективную реализацию методов, чем другие.
ArtOfWarfare

@Patrick: все методы используют прямые ссылки на значения и ключи, нет никакого преимущества в памяти для любого. За исключением Python 3 и .keys()` .values()возвращают словарные представления, которые являются легковесными.
Мартин Питерс

53

однострочная версия: (i - старый словарь, p - обратный словарь)

объяснение: i.keys()и i.values()возвращает два списка с ключами и значениями словаря соответственно. Функция zip имеет возможность связывать списки для создания словаря.

p = dict(zip(i.values(),i.keys()))

Предупреждение: это будет работать только в том случае, если значения являются хэшируемыми и уникальными.


Да, это будет работать: stackoverflow.com/questions/835092/…
The Unfun Cat

17
... и когда нет повторяющихся значений.
августа

3
Прекрасный. В отношении приведенного выше комментария, конечно, он работает только тогда, когда нет повторяющихся значений, но тогда вопрос, который запустил этот поток, предполагает, что у нас есть функция «один к одному», поэтому, учитывая это предположение, это наиболее элегантный ответ, безусловно.
Джон Стронг

1
Расширение хеш-значений: если ваши значения являются списками / наборами, преобразуйте их в кортеж, чтобы это работало (они все еще должны быть уникальными).
мюон

28
a = {'a':1,'b':2,'c':3}
{v:k for k, v in a.items()}[1]

или лучше

{k:v for k, v in a.items() if v == 1}

5
Что делать, если есть другой ключ, который содержит то же значение? Может быть, питонский путь. Но не очень хорошая идея.
7H3 IN5ID3R

Хорошая мысль, я добавил решение, которое работает с неуникальными значениями
Jelen

26
key = next((k for k in my_dict if my_dict[k] == val), None)

Могу ли я также иметь «еще» в этой же строке? Для случая, когда моя ценность не в значениях dict
Сришти Гупта

lKey = [k for k, v in lDictionary.iteritems() if v == lValue][0] or 'else-key'
Фахам


14

Я нашел этот ответ очень эффективным, но не очень легко читаемым для меня.

Чтобы сделать это более понятным, вы можете инвертировать ключ и значение словаря. Это сделать ключи значений и значения ключей, как показано здесь .

mydict = {'george':16,'amber':19}
res = dict((v,k) for k,v in mydict.iteritems())
print(res[16]) # Prints george

или

mydict = {'george':16,'amber':19}
dict((v,k) for k,v in mydict.iteritems())[16]

что по сути то же самое, что этот другой ответ .


Отлично!! Для Python3 это будут items () вместо iteritems () .
kkgarg

12

Если вы хотите найти ключ по значению, вы можете использовать словарь для создания словаря поиска, а затем использовать его, чтобы найти ключ по значению.

lookup = {value: key for key, value in self.data}
lookup[value]

11

Вы можете получить ключ, используя dict.keys(), dict.values()и list.index()методы, см примеры кода ниже:

names_dict = {'george':16,'amber':19}
search_age = int(raw_input("Provide age"))
key = names_dict.keys()[names_dict.values().index(search_age)]

2
Вы не используете определенный search_agevar в следующей строке ... Может быть, вам следует заменить valueна search_age?
Андерссон

2
Я получаю эту ошибку: объект 'dict_values' не имеет атрибута 'index'
Blue_Elephant

@Blue_Elephant Не могли бы вы предоставить фрагмент кода, в котором вы получили ошибку, и версию Python (также type(dict_values)было бы полезно распечатать )?
Андрей Иванейко

9

Вот мой взгляд на эту проблему. :) Я только начал изучать Python, поэтому я называю это:

Решение «Понятно для начинающих».

#Code without comments.

list1 = {'george':16,'amber':19, 'Garry':19}
search_age = raw_input("Provide age: ")
print
search_age = int(search_age)

listByAge = {}

for name, age in list1.items():
    if age == search_age:
        age = str(age)
        results = name + " " +age
        print results

        age2 = int(age)
        listByAge[name] = listByAge.get(name,0)+age2

print
print listByAge

,

#Code with comments.
#I've added another name with the same age to the list.
list1 = {'george':16,'amber':19, 'Garry':19}
#Original code.
search_age = raw_input("Provide age: ")
print
#Because raw_input gives a string, we need to convert it to int,
#so we can search the dictionary list with it.
search_age = int(search_age)

#Here we define another empty dictionary, to store the results in a more 
#permanent way.
listByAge = {}

#We use double variable iteration, so we get both the name and age 
#on each run of the loop.
for name, age in list1.items():
    #Here we check if the User Defined age = the age parameter 
    #for this run of the loop.
    if age == search_age:
        #Here we convert Age back to string, because we will concatenate it 
        #with the person's name. 
        age = str(age)
        #Here we concatenate.
        results = name + " " +age
        #If you want just the names and ages displayed you can delete
        #the code after "print results". If you want them stored, don't...
        print results

        #Here we create a second variable that uses the value of
        #the age for the current person in the list.
        #For example if "Anna" is "10", age2 = 10,
        #integer value which we can use in addition.
        age2 = int(age)
        #Here we use the method that checks or creates values in dictionaries.
        #We create a new entry for each name that matches the User Defined Age
        #with default value of 0, and then we add the value from age2.
        listByAge[name] = listByAge.get(name,0)+age2

#Here we print the new dictionary with the users with User Defined Age.
print
print listByAge

,

#Results
Running: *\test.py (Thu Jun 06 05:10:02 2013)

Provide age: 19

amber 19
Garry 19

{'amber': 19, 'Garry': 19}

Execution Successful!

9
get_key = lambda v, d: next(k for k in d if d[k] is v)

Хороший лайнер. Однако isследует использовать только для проверки равенства синглетонов ( None, Trueи Falseт. Д.). Тот факт, что CPython повторно использует строковые литералы (и, следовательно, a = 'foobar'; a is 'foobar'есть True), является деталью реализации и на него не следует полагаться.
piit79

1
И еще один комментарий: get_keyбудет выбрасывать, StopIterationесли значение не существует в словаре - было бы лучше использовать тот, next(..., None)который будет возвращаться, Noneесли значение не найдено.
piit79

Небольшая модификация будет работать, если словарь не содержит отдельных элементов, но устанавливает:get_first_key = lambda v, d: next((k for k in d if (v in d[k] is not None)), None)
сверхновая

7

Подумайте об использовании панд. Как говорится в «Питоне для анализа данных» Уильяма МакКинни

Другой способ думать о Серии - это упорядоченный диктат фиксированной длины, так как это отображение значений индекса на значения данных. Это может быть использовано во многих контекстах, где вы можете использовать диктовку.

import pandas as pd
list = {'george':16,'amber':19}
lookup_list = pd.Series(list)

Для запроса вашей серии сделайте следующее:

lookup_list[lookup_list.values == 19]

Что дает:

Out[1]: 
amber    19
dtype: int64

Если вам нужно сделать что-то еще с выводом, преобразование ответа в список может быть полезным:

answer = lookup_list[lookup_list.values == 19].index
answer = pd.Index.tolist(answer)

Он создатель панд. Хотя он более известен как Уэс.
Аксель

6

Здесь recovery_key принимает словарь и значение для поиска в словаре. Затем мы перебираем ключи в словаре, сравниваем их со значением и возвращаем этот ключ.

def recover_key(dicty,value):
    for a_key in dicty.keys():
        if (dicty[a_key] == value):
            return a_key


4
for name in mydict:
    if mydict[name] == search_age:
        print(name) 
        #or do something else with it. 
        #if in a function append to a temporary list, 
        #then after the loop return the list

1
Использование цикла for и append намного медленнее, чем понимание списка, и также дольше.
alexpinho98

3

на него ответили, но это можно сделать с помощью необычного использования «карта / уменьшение», например:

def find_key(value, dictionary):
    return reduce(lambda x, y: x if x is not None else y,
                  map(lambda x: x[0] if x[1] == value else None, 
                      dictionary.iteritems()))

3

Cat Plus Plus отметил, что словарь не предназначен для использования. Вот почему:

Определение словаря аналогично определению в математике. В этом случае dict - это отображение K (набора ключей) на V (значения), но не наоборот. Если вы разыменуете dict, вы ожидаете получить ровно одно возвращаемое значение. Но вполне допустимо, чтобы разные ключи отображались на одно и то же значение, например:

d = { k1 : v1, k2 : v2, k3 : v1}

Когда вы ищите ключ по его соответствующему значению, вы по существу инвертируете словарь. Но отображение не обязательно обратимо! В этом примере запрос ключа, соответствующего v1, может привести к k1 или k3. Вы должны вернуть оба? Только первый найден? Вот почему indexof () не определен для словарей.

Если вы знаете свои данные, вы можете сделать это. Но API не может предположить, что произвольный словарь является обратимым, отсюда и отсутствие такой операции.


3

вот мой взгляд на это. Это хорошо для отображения нескольких результатов на тот случай, если вам нужен один из них. Так что я добавил список, а также

myList = {'george':16,'amber':19, 'rachel':19, 
           'david':15 }                         #Setting the dictionary
result=[]                                       #Making ready of the result list
search_age = int(input('Enter age '))

for keywords in myList.keys():
    if myList[keywords] ==search_age:
    result.append(keywords)                    #This part, we are making list of results

for res in result:                             #We are now printing the results
    print(res)

И это все...


3
d= {'george':16,'amber':19}

dict((v,k) for k,v in d.items()).get(16)

Вывод следующий:

-> prints george

[к для K, V в d.Items () , если v == 16]
AURO

3

Нет простого способа найти ключ в списке путем «поиска» значения. Однако, если вы знаете значение, перебирая ключи, вы можете искать значения в словаре по элементу. Если D [элемент], где D - объект словаря, равен ключу, который вы пытаетесь найти, вы можете выполнить некоторый код.

D = {'Ali': 20, 'Marina': 12, 'George':16}
age = int(input('enter age:\t'))  
for element in D.keys():
    if D[element] == age:
        print(element)

3

Вам нужно использовать словарь и наоборот этого словаря. Это означает, что вам нужна другая структура данных. Если вы используете Python 3, используйте enumмодуль, но если вы используете Python 2.7, используйтеenum34 который обратно портирован для Python 2.

Пример:

from enum import Enum

class Color(Enum): 
    red = 1 
    green = 2 
    blue = 3

>>> print(Color.red) 
Color.red

>>> print(repr(Color.red)) 
<color.red: 1=""> 

>>> type(Color.red) 
<enum 'color'=""> 
>>> isinstance(Color.green, Color) 
True 

>>> member = Color.red 
>>> member.name 
'red' 
>>> member.value 
1 


2

Просто мой ответ в lambdaа filter.

filter( lambda x, dictionary=dictionary, search_age=int(search_age): dictionary[x] == search_age  , dictionary )

1

уже ответили, но так как несколько человек упомянули обратный словарь, вот как вы делаете это в одну строку (при условии отображения 1: 1) и некоторые различные данные перфорации:

Python 2.6:

reversedict = dict([(value, key) for key, value in mydict.iteritems()])

2.7+:

reversedict = {value:key for key, value in mydict.iteritems()}

если вы думаете, что это не 1: 1, вы все равно можете создать разумное обратное отображение с помощью пары строк:

reversedict = defaultdict(list)
[reversedict[value].append(key) for key, value in mydict.iteritems()]

насколько это медленно: медленнее, чем простой поиск, но далеко не так медленно, как вы думаете - в «прямом» словаре из 100000 записей, «быстрый» поиск (т. е. поиск значения, которое должно быть в начале ключа) был примерно в 10 раз быстрее, чем обратный просмотр всего словаря, а «медленный» поиск (ближе к концу) - в 4–5 раз быстрее. Таким образом, после примерно 10 поисков он окупается.

вторая версия (со списками на элемент) занимает примерно в 2,5 раза больше, чем простая версия.

largedict = dict((x,x) for x in range(100000))

# Should be slow, has to search 90000 entries before it finds it
In [26]: %timeit largedict.keys()[largedict.values().index(90000)]
100 loops, best of 3: 4.81 ms per loop

# Should be fast, has to only search 9 entries to find it. 
In [27]: %timeit largedict.keys()[largedict.values().index(9)]
100 loops, best of 3: 2.94 ms per loop

# How about using iterkeys() instead of keys()?
# These are faster, because you don't have to create the entire keys array.
# You DO have to create the entire values array - more on that later.

In [31]: %timeit islice(largedict.iterkeys(), largedict.values().index(90000))
100 loops, best of 3: 3.38 ms per loop

In [32]: %timeit islice(largedict.iterkeys(), largedict.values().index(9))
1000 loops, best of 3: 1.48 ms per loop

In [24]: %timeit reversedict = dict([(value, key) for key, value in largedict.iteritems()])
10 loops, best of 3: 22.9 ms per loop

In [23]: %%timeit
....: reversedict = defaultdict(list)
....: [reversedict[value].append(key) for key, value in largedict.iteritems()]
....:
10 loops, best of 3: 53.6 ms per loop

Также были некоторые интересные результаты с ifilter. Теоретически ifilter должен быть быстрее, так как мы можем использовать itervalues ​​() и, возможно, не нужно создавать / просматривать весь список значений. На практике результаты были ... странными ...

In [72]: %%timeit
....: myf = ifilter(lambda x: x[1] == 90000, largedict.iteritems())
....: myf.next()[0]
....:
100 loops, best of 3: 15.1 ms per loop

In [73]: %%timeit
....: myf = ifilter(lambda x: x[1] == 9, largedict.iteritems())
....: myf.next()[0]
....:
100000 loops, best of 3: 2.36 us per loop

Таким образом, для небольших смещений это было значительно быстрее, чем в любой предыдущей версии (2.36 * u * S против минимума 1.48 * m * S для предыдущих случаев). Тем не менее, для больших смещений в конце списка, он был значительно медленнее (15,1 мс против тех же 1,48 мс). Небольшая экономия на нижнем конце не стоит затрат на верхнем, imho.


Я очень хочу, чтобы это (reversedict = defaultdict (список) reversedict [значение] .append (ключ) для ключа, значение в largedict.iteritems ()]) работало, но, используя Python 2.7.3, я получаю синтаксическую ошибку для слова 'для'
Слэшдоттир

это то, что вы на самом деле напечатали? вы пропустите [в нем, если это так. в противном случае, убедитесь, что он в двух строках, или вставьте ;между ними, если это не так.
Корли Бригман

1

Иногда может понадобиться int ():

titleDic = {'Фильмы':1, 'Музыка':2}

def categoryTitleForNumber(self, num):
    search_title = ''
    for title, titleNum in self.titleDic.items():
        if int(titleNum) == int(num):
            search_title = title
    return search_title

1

Вот решение, которое работает как в Python 2, так и в Python 3:

dict((v, k) for k, v in list.items())[search_age]

Часть before [search_age]создает обратный словарь (где значения являются ключами и наоборот). Вы можете создать вспомогательный метод, который будет кешировать этот обратный словарь следующим образом:

def find_name(age, _rev_lookup=dict((v, k) for k, v in ages_by_name.items())):
    return _rev_lookup[age]

или, в более общем случае, фабрика, которая создала бы метод поиска по возрасту для одного или нескольких списков.

def create_name_finder(ages_by_name):
    names_by_age = dict((v, k) for k, v in ages_by_name.items())
    def find_name(age):
      return names_by_age[age]

так что вы сможете сделать:

find_teen_by_age = create_name_finder({'george':16,'amber':19})
...
find_teen_by_age(search_age)

Обратите внимание , что я переименовал listв ages_by_nameтак бывший предопределенный тип.


1

Вот как вы получаете доступ к словарю, чтобы делать то, что вы хотите:

list = {'george': 16, 'amber': 19}
search_age = raw_input("Provide age")
for age in list:
    if list[age] == search_age:
        print age

конечно, твои имена настолько необычны, что кажется, что это будет печать эпохи, но она печатает имя. Поскольку вы обращаетесь по имени, становится понятнее, если вы напишите:

list = {'george': 16, 'amber': 19}
search_age = raw_input("Provide age")
for name in list:
    if list[name] == search_age:
        print name

Еще лучше:

people = {'george': {'age': 16}, 'amber': {'age': 19}}
search_age = raw_input("Provide age")
for name in people:
    if people[name]['age'] == search_age:
        print name

1
dictionary = {'george' : 16, 'amber' : 19}
search_age = raw_input("Provide age")
key = [filter( lambda x: dictionary[x] == k  , dictionary ),[None]][0] 
# key = None from [None] which is a safeguard for not found.

Для нескольких случаев используйте:

keys = [filter( lambda x: dictionary[x] == k  , dictionary )]

*** NameError: global name 'dictionary' is not defined
Бишвас Мишра

filter( lambda x, dictionary=dictionary, search_age=int(search_age): dictionary[x] == search_age , dictionary )
Бишвис Мишра
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.