Ben Smith
Ben Smith

Reputation: 380

checks if value in a dictionaries list values is unique

I have two dictionaries:

mems = {'member2': ['PCP1', 'PCP2'],
  'member6': ['PCP1', 'PCP5'],
  'member7': ['PCP2', 'PCP4', 'PCP5'],
  'member9': ['PCP1', 'PCP5']}


provs = {'PCP1': 2, 'PCP2': 1, 'PCP4': 1, 'PCP5': 1}

How do I check if an item in these list values is unique and doesnt appear anywhere else in the dictionary? If it is unique append the 'member' and that 'PCP' to a new dictionary called matches = dict() remove that member from 'mems' and then add any other leftover 'PCP' to their count in provs. Oh, and remove the one that is unique in mems

result should look like

mems = {'member2': ['PCP1', 'PCP2'],
'member6': ['PCP1', 'PCP5'],
'member7': ['PCP2', 'PCP4', 'PCP5'],
'member9': ['PCP1', 'PCP5']}

provs = {'PCP1': 2, 'PCP2': 2, 'PCP5': 2}

matches = ['member7' : 'PCP4'}

I have no idea how to do this. I tried starting making a counter for the PCPs in mems and finding the ones with 1 and then adding to matches but it gives the wrong member, 'member9'.

pcpCounts = dict(collections.Counter(itertools.chain.from_iterable(new_members.values())))
print(pcpCounts)
{'PCP1': 3, 'PCP2': 2, 'PCP4': 1, 'PCP5': 2}

for (k,v), (k2,v2) in zip(memsCounter.items(), pcpCounter.items()):
if v2 == 1:
    match[k] = k2

print(matches)
{'member9': 'PCP4'}

Why does the integrity not maintain when I do this. More importantly, is there a better way to do it?

Upvotes: 0

Views: 84

Answers (4)

eatmeimadanish
eatmeimadanish

Reputation: 3907

To start it might make it easier if you create a set to see if it exists first, and then if it does, find the members it belongs to.

mems = {'member2': ['PCP1', 'PCP2'],
  'member6': ['PCP1', 'PCP5'],
  'member7': ['PCP2', 'PCP4', 'PCP5'],
  'member9': ['PCP1', 'PCP5']}

provs = {'PCP1': 2, 'PCP2': 1, 'PCP4': 1, 'PCP5': 1}

members = {}
x = set()
for k,v in mems.items():
    for i in v:
        x.add(i)
        try:
            members[i].append(k)
        except KeyError:
            members[i] = [k,]

print(x)
print(members)

for k in provs:
    if k in x:
        print('KEY: {0} in {1}'.format(k,members[k]))
        print('KEY: {0} in {1}'.format(k,len(members[k]))) # count the references. 

Upvotes: 2

jferard
jferard

Reputation: 8180

mems = {'member2': ['PCP1', 'PCP2'],
  'member6': ['PCP1', 'PCP5'],
  'member7': ['PCP2', 'PCP4', 'PCP5'],
  'member9': ['PCP1', 'PCP5']}

provs = {'PCP1': 2, 'PCP2': 1, 'PCP4': 1, 'PCP5': 1}

First, you can reverse mems:

d1 = {}
for mem, pcps in mems.items():
    for pcp in pcps:
        d1.setdefault(pcp, []).append(mem)

# {'PCP1': ['member2', 'member6', 'member9'], 'PCP2': ['member2', 'member7'], 'PCP5': ['member6', 'member7', 'member9'], 'PCP4': ['member7']}

It's now easy to compute the matches: filter the dict on len(mems) and reverse it again:

matches = {mems[0]: pcp for pcp, mems in d1.items() if len(mems) == 1}
# {'member7': 'PCP4'}

You can create a new_mems dict that does not contain the matching pcp:

new_mems = {mem: [pcp for pcp in pcps if pcp not in matches.values()] for mem, pcps in mems.items()}
# {'member2': ['PCP1', 'PCP2'], 'member6': ['PCP1', 'PCP5'], 'member7': ['PCP2', 'PCP5'], 'member9': ['PCP1', 'PCP5']}

And then use a counter to update provs:

import collections
new_provs = collections.Counter(provs)
for mem, pcp in matches.items():
    new_provs.update(mems[mem]) # add 1 to each count
    new_provs[pcp] -= 2 # was 0 + 1 (line above) -> is 0

# Counter({'PCP1': 2, 'PCP2': 2, 'PCP5': 2, 'PCP4': 0})

Upvotes: 1

Jab
Jab

Reputation: 27485

You can still use the Counter here just turn it into a set filtered by only the single occurring provs then use set.intersection as your check.

from collections import Counter

mems = {'member2': ['PCP1', 'PCP2'], 'member6': ['PCP1', 'PCP5'], 'member7': ['PCP2', 'PCP4', 'PCP5'], 'member9': ['PCP1', 'PCP5']}
provs = {'PCP1': 2, 'PCP2': 1, 'PCP4': 1, 'PCP5': 1}

#I'm using sum here instead of itertools.chain. It serves the same purpose
prov_counts = {p for p, c in Counter(sum(mems.values(), [])).items() if c == 1} 
prov_filter = map(prov_counts.intersection, mems.values())

matches = {m: list(p) for m, p in zip(mems, prov_filter) if p}

print(matches)

{'member7': ['PCP4']}

Upvotes: 2

gseva
gseva

Reputation: 373

One way of doing that is changing the second part of your code. Once you have the counts, you can iterate over original values checking which value is unique.

>>> mems = {'member2': ['PCP1', 'PCP2'],
... 'member6': ['PCP1', 'PCP5'],
... 'member7': ['PCP2', 'PCP4', 'PCP5'],
... 'member9': ['PCP1', 'PCP5']}
>>> import collections, itertools
>>> pcpCounts = collections.Counter(itertools.chain.from_iterable(mems.values()))
>>> matches = {}
>>> for k, pcps in mems.items():
...     for v in pcps:
...         if pcpCounts[v] == 1:
...             matches[k] = v
...
>>> matches
{'member7': 'PCP4'}

Upvotes: 1

Related Questions