Удалить кортежи из списка значений словаря, если первое значение в кортеже = 1

Сначала у меня есть словарь d1, который выглядит так:

d1 = {    'w' : ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'c', 'c'],
          'x' : ['d', 'd', 'd', 'e', 'e'],
          'y' : ['f', 'f', 'g'],
          'z' : ['h', 'i']    
     }

Затем я перебираю этот словарь и создаю новый словарь, в котором каждое значение представляет собой список с двумя элементами: целым числом и списком. Целое число — это количество строк в этом значении в d1. Список содержит кортежи, каждый кортеж содержит строку из d1 (в позиции 1 кортежа) и количество раз, которое эта строка появлялась в d1 (в позиции 0 кортежа):

d2 = {   'w' : [10, [(5, 'a'), (3, 'b'), (2, 'c')], 
         'x' : [5, [(3, 'd'), (2, 'e')],
         'y' : [3, [(2, 'f'), (1, 'g')],
         'z' : [2, [(1, 'h'), (1, 'i')]    
     }

Я хочу удалить любую строку, которая появляется только один раз («g», «h» и «i»), поэтому в конце я хочу:

dFinal = {    'w' : [10, [(5, 'a'), (3, 'b'), (2, 'c')],
              'x' : [5, [(3, 'd'), (2, 'e')],
              'y' : [2, [(2, 'f')]    
         }

Я прочитал пример того, что я считаю пониманием словаря для удаления целых элементов словаря, если длина списка значений меньше 2:

d = {k:v for k,v in d.items() if len(v) > 1}

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

Сначала я пытался написать функцию, которая принимала бы d2 в качестве аргумента, но я не знаю, как ссылаться на позицию 0 каждого кортежа.

Затем подумал, что, вероятно, будет намного проще сделать dFinal, используя d1, у которого нет кортежей, вместо того, чтобы пытаться изменить d2.

Если бы вы могли описать чистый способ сделать dFinal из d1 и/или d2 и объяснить ход мысли, я был бы очень признателен. Оба действительно помогли бы мне понять, как точно манипулировать списками словарей с включениями.

Спасибо!


person lambda_xx    schedule 20.02.2017    source источник


Ответы (4)


Ну, вы можете использовать объект Counter. Честно говоря, я бы использовал циклы, потому что это, вероятно, будет более эффективным.

In [1]: from collections import Counter

Правка: вот как бы я это сделал, без понимания:

In [17]: for k,v in d1.items():
    ...:     counts = Counter(v)
    ...:     counts = [t for t in counts.items() if t[1] > 1]
    ...:     if len(counts) > 0:
    ...:         dfinal[k] = [sum(c[1] for c in  counts), counts]
    ...:

In [18]: dfinal
Out[18]:
{'w': [10, [('b', 3), ('a', 5), ('c', 2)]],
 'x': [5, [('d', 3), ('e', 2)]],
 'y': [2, [('f', 2)]]}
person juanpa.arrivillaga    schedule 20.02.2017
comment
Большое спасибо. Однако это не корректирует целое число суммы в начале каждого значения. 10, 5 и 3 должны стать 10, 5 и 2. - person lambda_xx; 21.02.2017
comment
@ddrsee пропустил это. Легко исправить. - person juanpa.arrivillaga; 21.02.2017
comment
Бесконечно благодарен. Я буду читать о Counter и пытаться понять, что здесь происходит... - person lambda_xx; 21.02.2017
comment
Это не сохраняет порядок списка, хотя, если это важно, сортировка должна быть довольно тривиальной. - person TemporalWolf; 21.02.2017
comment
@ddrsee объект Counter — это всего лишь подкласс dict, специализированный для подсчета. - person juanpa.arrivillaga; 21.02.2017
comment
Да, я бы сделал это так же, как когда делал d2: for k,v в d2.items: v[1].sort(key=lambda tup: tup[0], reverse=True) - person lambda_xx; 21.02.2017
comment
@ddrsee просто используйте counts = [t for t in counts.most_common() if t[1] > 1] - person juanpa.arrivillaga; 21.02.2017
comment
@juanpa.arrivillaga О, здорово. Спасибо. - person lambda_xx; 21.02.2017

Я не думаю, что это хороший стиль, но вы можете сделать это так:

dFinal = {k: [sum([i for i, c in v[1] if i != 1]),
              [(i, c) for i, c in v[1] if i != 1]]
          for k, v in d2.items()
          if [(i, c) for i, c in v[1] if i != 1]}

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

person Tom Lynch    schedule 20.02.2017
comment
Это не корректирует счетчики членства 'y': [3, [(2, 'f')]], хотя на самом деле они, вероятно, не нужны. - person TemporalWolf; 21.02.2017
comment
Они необходимы! :-) - person lambda_xx; 21.02.2017
comment
Большое спасибо. Есть ли у вас любимый учебник по таким пониманиям (и что такое рефакторинг)? - person lambda_xx; 21.02.2017
comment
@ddrsee, я думаю, Том пытается сказать не используйте такие понимания - person juanpa.arrivillaga; 21.02.2017
comment
@juanpa.arrivillaga Не могли бы вы вкратце рассказать о причинах, по которым этого делать не следует? Я думал, что они более эффективны, чем циклы, но я очень новичок в этом. - person lambda_xx; 21.02.2017
comment
@ddrsee они могут быть, но обратите внимание, что в этом случае вы используете [(i, c) for i, c in v[1] if i != 1] три раза в понимании. Поскольку вы не можете сохранить это как промежуточное значение, как в цикле, создаются три отдельных списка! Но вам действительно нужно создать его только один раз, если вы думаете об этом. Кроме того, подобные включения полностью убивают читабельность, и этим действительно не стоит жертвовать ради незначительного прироста скорости. - person juanpa.arrivillaga; 21.02.2017
comment
@juanpa.arrivillaga прав насчет того, почему не стоит писать пояснения, которые усложняют ситуацию. Обычно код приходится читать чаще, чем писать. Я не буду понимать код с первого взгляда через полгода, к тому же он менее эффективен, чем мог бы быть. - person Tom Lynch; 21.02.2017

Другой вариант: медленнее, но проще:

def has_dupe(lst):
    return any([x[0] > 1 for x in lst[1]])

def reduce_list(lst):
    result = [(x, y) for (x, y) in lst[1] if x > 1]
    return [sum([x for (x, y) in result]), result]

d = {key: reduce_list(value) for key, value in d2.items() if has_dupe(value)}
person TemporalWolf    schedule 20.02.2017

Этот

from collections import Counter

d2 = {k: [len(v), sorted(Counter(v).items())] for k, v in d1.items()}

dFinal = {k: [v1, [(y, x) for x, y in v2 if y > 1]] for k, (v1, v2) in d2.items()}

Мои d2 и dFinal немного отличаются от ваших. Это мой d2

{'w': [10, [('a', 5), ('b', 3), ('c', 2)]],
 'x': [5, [('d', 3), ('e', 2)]],
 'y': [3, [('f', 2), ('g', 1)]],
 'z': [2, [('h', 1), ('i', 1)]]}

это мой dFinal

{'w': [10, [(5, 'a'), (3, 'b'), (2, 'c')]],
 'x': [5, [(3, 'd'), (2, 'e')]],
 'y': [3, [(2, 'f')]],
 'z': [2, []]}

но вы можете легко исправить это самостоятельно.

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

person Elmex80s    schedule 20.02.2017
comment
Превратить ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'c', 'c'] в этого [('a', 5), ('b', 3), ('c', 2)]. - person Elmex80s; 21.02.2017
comment
Для этого не нужно преобразовывать объект Counter в объект dict. Просто удалите вызов dict, и вы увидите, что он работает точно так же. - person juanpa.arrivillaga; 21.02.2017
comment
Да, я обновил его. Ты прав. Я играю с крошечным терминалом, поэтому пропустил :-) - person Elmex80s; 21.02.2017