1

I have the following Python tuple:

my_tuple = ( "key1", ("nested_key1", "nested_key2"), "key3")

I am required to test if a dict contains either "key1", all elements in ("nested_key1", "nested_key2"), or "key3". If there is a match for all elements at any of the root tuple's indexs, then the algorithm should only evaluate as True if there are NO other matches in other indexes. If there are additional keys not specified in the tuple, these can be disregarded for matching purposes.

Meaning that...

These should return True:

matching_dict_root = {"key1": 1}
matching_dict_nested = {"nested_key1": 2, "nested_key2": 3}
unspecified_keys_are_allowed = {"key1": 1, "99problems": 99}

These should be False:

too_many = {"key1": 1, "nested_key1": 1, "nested_key2": 2}
also_wrong = {"key1": 1, "nested_key2": 1}

Can assume (for my current case, but general solutions are most welcome):

  • only 1 nested level
  • all individual attributes (at all depths) are globally unique

Python-3.6 please, Python-2.7 also helpful, but not required. If possible (I presume it is, and that I'll kick myself), std. lib. only.

jonathan
  • 784
  • 1
  • 10
  • 27

2 Answers2

1

So like:

sum( keys in D if not isinstance(keys, tuple) 
               else all( key in D for key in keys )
     for keys in my_tuple ) == 1

The reason for sum( ) == 1 rather than any( ) is:

only evaluate as True if there are NO other matches in other indexes

Which means that there should only be one index in my_tuple that is True the rest must be False.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
1

Been driving me nuts, but here it is...

To get a flattened list of the tuple, we use this helper function from Flatten (an irregular) list of lists:

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el

This will allow for testing invalid intersections using frozenset.isdisjoint() against a sublist of the flattened list.

The final solution, after expanding @DanD.'s helpful solution, is therefore:

def all_attributes_exist_with_null_intersection(iterable, d):
    return sum((i in d if not isinstance(i, tuple) else (all(sub_i in d for sub_i in i)))
               and frozenset(d.keys()).isdisjoint(flatten([x for x in i if x != i]))
               for i in iterable) == 1

Tests:

sample_tuple = ("key1", ("nested_key1", "nested_key2"), "key3")

true_tests = [
    {"key1": 1},
    {"nested_key1": 2, "nested_key2": 3},
    {"key1": 1, "99problems": 99}
]

false_tests = [
    {"key1": 1, "nested_key1": 1, "nested_key2": 2},
    {"key1": 1, "nested_key2": 1}
]

trues = [all_attributes_exist_with_null_intersection(sample_tuple, d) for d in true_tests]
falses = [all_attributes_exist_with_null_intersection(sample_tuple, d) for d in false_tests]

print("Trues:\n{trues}\n\nFalses:\n{falses}".format(
    trues=trues,
    falses=falses
)

Output:

Trues:
[True, True, True]

Falses:
[False, False]

Caveats:

  • Python 3.3 required for yield from in helper function
jonathan
  • 784
  • 1
  • 10
  • 27