Check if elemet of a set exists in a multikey dictionary in Python?

Viewed 86

I have a dictionary and a set as follows.

tmp_dict = {1: {'ABCD': 1, 'EFGH': 2}, 2: {'ABCW': 0, 'DEFG': 0}}
s = set(['W','J'])

What I am trying to do is to check if (or which) element of set s exists in the tmp_dict values and filter the associated key and value. This is what I am doing presently, however, I am trying to see if there is any way to reduce the complexity of my approach as I am dealing with a very large set and dictionary.

for key in tmp_dict.keys():
    for elem in s:
        if any(elem in k for k in tmp_dict[key].keys()):
            print(key, tmp_dict[key], elem)

2 {'ABCW': 0, 'DEFG': 0} W
4 Answers

Invert your dictionary into one that has all the letters as keys.

from collections import defaultdict

tmp_dict2 = defaultdict(lambda: defaultdict(list))
for key, subdict in tmp_dict.items():
    for letters in subdict.keys():
        for letter in letters:
            tmp_dict2[letter].append((key, subdict))

for elem in s:
    if elem in tmp_dict2:
        for key, subdict in tmp_dict2[elem]:
            print(key, subdict, elem)

Here's one approach using pandas and numpy.

import pandas as pd
import numpy as np

tmp_dict = {1: {'ABCD': 1, 'EFGH': 2}, 2: {'ABCW': 0, 'DEFG': 0}, 3: {'AW': 1, 'ZXY': 0}}

# Extract your letters with pandas
letters = pd.DataFrame(tmp_dict).index.to_numpy(dtype='<U5') 

>>> letters
    array(['ABCD', 'EFGH', 'ABCW', 'DEFG', 'AW', 'ZXY'], dtype='<U5')

# Create a lookup table with a pandas Series, where index are tmp_dict.keys()
# and values are the dictionaries
lookup_table = pd.Series(tmp_dict).apply(dict)

>>> lookup_table
    1    {'ABCD': 1, 'EFGH': 2}
    2    {'ABCW': 0, 'DEFG': 0}
    3       {'AW': 1, 'ZXY': 0}
    dtype: object

s = set(['W','J'])

result = {}
# Iterate over your wanted characters
for elem in s:
    # Find the indices where the substring elem is present in any of letters
    found = np.flatnonzero(np.core.defchararray.find(letters, elem) != -1)
    lookfor = set(letters[found])

    # In the first iteration, elem='W', lookfor is {'ABCW', 'AW'}

    # If an index was found, then search your tmp_dict for the key, value pair
    # with any preferred algorithm of your choice. You could use a simple 
    # lookup with a for-loop and your original tmp_dict, but I'd go with this
    # for a faster implementation
    if found.size > 0: 
        found = lookup_table.map(lookfor.intersection).astype(bool).values
        indices = lookup_table.index[found]
        values = lookup_table[found]
        result[elem] = list(zip(indices, values))

Now, if you print result

for elem, values in result.items():
    for value in values:
        print(value[0], value[1], elem)

You get

2 {'ABCW': 0, 'DEFG': 0} W
3 {'AW': 1, 'ZXY': 0} W

Here's a different approach

#Get list of keys concatenated as strings to make search easier
keys_concat = ["".join(x.keys()) for x in list(tmp_dict.values())]
keys_concat
['ABCDEFGH', 'ABCWDEFG']

# Avoid a nested `any` and a nested `.keys()` compared to original code.
for index, key in enumerate(keys_concat):
    for sub in s:
        if(sub in key):
            print(index+1, tmp_dict[index+1], sub)


2 {'ABCW': 0, 'DEFG': 0} W

Another solution, slight variation of @Barmar answer:

tmp_dict = {1: {"ABCD": 1, "EFGH": 2}, 2: {"ABCW": 0, "DEFG": 0}}
s = set(["W", "J"])

# create an index
index = {}
for k, v in tmp_dict.items():
    for ch in set("".join(v.keys())):
        index.setdefault(ch, []).append(k)

# now use the index in search:
for ch in s:
    for v in index.get(ch, []):
        print(v, tmp_dict[v], ch)

Prints:

2 {'ABCW': 0, 'DEFG': 0} W
Related