Как отфильтровать словарь по значению? - PullRequest
5 голосов
/ 06 августа 2009

Вопрос новичку здесь, поэтому, пожалуйста, потерпите меня.

Допустим, у меня есть словарь, похожий на этот:

a = {"2323232838": ("first/dir", "hello.txt"),
     "2323221383": ("second/dir", "foo.txt"),
     "3434221": ("first/dir", "hello.txt"),
     "32232334": ("first/dir", "hello.txt"),
     "324234324": ("third/dir", "dog.txt")}

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

matched = {"2323232838": ("first/dir", "hello.txt"),
           "3434221":    ("first/dir", "hello.txt"),
           "32232334":   ("first/dir", "hello.txt")}

А остальные непревзойденные элементы должны выглядеть так:

remainder = {"2323221383": ("second/dir", "foo.txt"),
             "324234324":  ("third/dir", "dog.txt")}

Заранее спасибо, и если вы предоставите пример, пожалуйста, прокомментируйте его как можно больше.

Ответы [ 5 ]

10 голосов
/ 07 августа 2009

Приведенный ниже код приведет к двум переменным, matches и remainders. matches - это массив словарей, в котором соответствующие элементы из исходного словаря будут иметь соответствующий элемент. remainder будет содержать, как в вашем примере, словарь, содержащий все несопоставимые элементы.

Обратите внимание, что в вашем примере есть только один набор совпадающих значений: ('first/dir', 'hello.txt'). Если бы было более одного набора, каждый имел бы соответствующую запись в matches.

import itertools

# Original dict
a = {"2323232838": ("first/dir", "hello.txt"),
     "2323221383": ("second/dir", "foo.txt"),
     "3434221": ("first/dir", "hello.txt"),
     "32232334": ("first/dir", "hello.txt"),
     "324234324": ("third/dir", "dog.txt")}

# Convert dict to sorted list of items
a = sorted(a.items(), key=lambda x:x[1])

# Group by value of tuple
groups = itertools.groupby(a, key=lambda x:x[1])

# Pull out matching groups of items, and combine items   
# with no matches back into a single dictionary
remainder = []
matched   = []

for key, group in groups:
   group = list(group)
   if len(group) == 1:
      remainder.append( group[0] )
   else:
      matched.append( dict(group) )
else:
   remainder = dict(remainder)

Выход:

>>> matched
[
  {
    '3434221':    ('first/dir', 'hello.txt'), 
    '2323232838': ('first/dir', 'hello.txt'), 
    '32232334':   ('first/dir', 'hello.txt')
  }
]

>>> remainder
{
  '2323221383': ('second/dir', 'foo.txt'), 
  '324234324':  ('third/dir', 'dog.txt')
}

Будучи новичком, вы, вероятно, знакомитесь с несколькими незнакомыми концепциями в приведенном выше коде. Вот несколько ссылок:

4 голосов
/ 07 августа 2009

То, что вы просите, называется «Перевернутый указатель» - отдельные элементы записываются только один раз со списком ключей.

>>> from collections import defaultdict
>>> a = {"2323232838": ("first/dir", "hello.txt"),
...      "2323221383": ("second/dir", "foo.txt"),
...      "3434221": ("first/dir", "hello.txt"),
...      "32232334": ("first/dir", "hello.txt"),
...      "324234324": ("third/dir", "dog.txt")}
>>> invert = defaultdict( list )
>>> for key, value in a.items():
...     invert[value].append( key )
... 
>>> invert
defaultdict(<type 'list'>, {('first/dir', 'hello.txt'): ['3434221', '2323232838', '32232334'], ('second/dir', 'foo.txt'): ['2323221383'], ('third/dir', 'dog.txt'): ['324234324']})

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

Теперь, чтобы получить ваши пересмотренные словари из этого.

Фильтрация:

>>> [ invert[multi] for multi in invert if len(invert[multi]) > 1 ]
[['3434221', '2323232838', '32232334']]
>>> [ invert[uni] for uni in invert if len(invert[uni]) == 1 ]
[['2323221383'], ['324234324']]

Расширение

>>> [ (i,multi) for multi in invert if len(invert[multi]) > 1 for i in invert[multi] ]
[('3434221', ('first/dir', 'hello.txt')), ('2323232838', ('first/dir', 'hello.txt')), ('32232334', ('first/dir', 'hello.txt'))]
>>> dict( (i,multi) for multi in invert if len(invert[multi]) > 1 for i in invert[multi] )
{'3434221': ('first/dir', 'hello.txt'), '2323232838': ('first/dir', 'hello.txt'), '32232334': ('first/dir', 'hello.txt')}

Подобная (но более простая) обработка работает для предметов, которые встречаются один раз.

1 голос
/ 07 августа 2009

Я предполагаю, что ваш уникальный идентификатор будет ключом.
Возможно, не очень красивый, но возвращает дикт с вашими уникальными значениями:

>>> dict_ = {'1': ['first/dir', 'hello.txt'],
'3': ['first/dir', 'foo.txt'], 
'2': ['second/dir', 'foo.txt'], 
'4': ['second/dir', 'foo.txt']}  
>>> dict((v[0]+v[1],k) for k,v in dict_.iteritems())  
{'second/dir/foo.txt': '4', 'first/dir/hello.txt': '1', 'first/dir/foo.txt': '3'}  

Я видел, как вы обновили свой пост:

>>> a
{'324234324': ('third/dir', 'dog.txt'), 
'2323221383': ('second/dir', 'foo.txt'), 
'3434221': ('first/dir', 'hello.txt'), 
'2323232838': ('first/dir', 'hello.txt'), 
'32232334': ('first/dir', 'hello.txt')}
>>> dict((v[0]+"/"+v[1],k) for k,v in a.iteritems())
{'second/dir/foo.txt': '2323221383', 
'first/dir/hello.txt': '32232334', 
'third/dir/dog.txt': '324234324'}
1 голос
/ 07 августа 2009

Итерация по словарю ничем не отличается от итерации по списку в python:

for key in dic:
    print("dic[%s] = %s" % (key, dic[key]))

Это напечатает все ключи и значения вашего словаря.

0 голосов
/ 07 августа 2009

, если вы знаете, какое значение вы хотите отфильтровать:

known_tuple = 'first/dir','hello.txt'
b = {k:v for k, v in a.items() if v == known_tuple}

тогда a станет:

a = dict(a.items() - b.items())

это нотация py3k, но я уверен, что нечто подобное может быть реализовано в старых версиях. Если вы не знаете, что такое known_tuple, вам сначала нужно это выяснить. например, вот так:

c = list(a.values())
for i in set(c):
    c.remove(i)
known_tuple = c[0]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...