Check and return dictionary items based on given key-value pairs

#1

Hi all, I am having issues with querying of dictionary items against a given set of key-value pairs. I am trying to do a ‘skimming’ or reduction method in dictionaries in which it will returns me a set of results from a given set of condition.

This is my code:

conditions = {'menuA':['a200']}
# Give me items that are menuA/a100 + menuB/b100, or menuA/a100 + menuB/b200

my_items = [
    {'item01' : {'menuA': ['a100'], 'menuB': ['b200']}},
    {'item02' : {'menuA': ['a200'], 'menuB': ['b200'], 'menuC' : ['c100']}},
    {'item03' : {'menuA': ['a100'], 'menuB': ['b100', 'b200']}},
    {'item04' : {'menuA': ['a100', 'a200']}},
    {'item05' : {'menuB': ['b100'], 'menuC': ['c100']}}
]

result = []

for m in my_items:
   for mk, mv in m.items():
      all_conditions_met = len(conditions) == len(mv) #False
      for condition in conditions:
         if condition in mv:
            all_conditions_met &= bool((set(mv[condition]) & set(conditions[condition])))

            # Tried the following but it will fail if given condition eg. conditions = {'menuA': ['a100', 'a200'], 'menuB': ['b200']}
            # where it should return me ['item01', 'item03'] but it gives me [] instead...
            # all_conditions_met &= bool((set(mv[condition]) & set(conditions[condition]))) & (len(mv[condition]) == len(conditions[condition]))

         else:
            all_conditions_met = False
            break
      if all_conditions_met:
         result.append(mk)

print result # 'item04'

In my above example, given the condition {'menuA':['a200']}, it should not be returning me any results as there are no items that consists only of {'menuA':['a200']} but yet it return me an item - closest to the given condition…

The idea behind this is to simply returns me items based on the conditions.

Could anyone give me better insights for this?

#2

Hmm, I’m not quite understanding your logic requirements.
First you say {'menuA':['a200']} should return nothing. This seems to mean that you only want items where menuA contains exactly one item that is 'a200'

But then in your code, you provide this second example:
{'menuA': ['a100', 'a200'], 'menuB': ['b200']}

So, I would expect that the first condition would want an item that contains 'menuA' with exactly 2 items that are ['a100', 'a200'] in any order. There is one item that matches that, and it’s item04
Then, you also want an item that contains 'menuB' with exactly one entry that is 'b200'
That matches item01 and item03
So there’s two choices here …

  1. You could take every item that matches any one of the conditions and get ['item04', 'item01', 'item03']
  2. You could take every item that matches all conditions and get []

Neither of those choices match the ['item01', 'item03'] from your comment, so something is getting lost somewhere.

Could you walk me through the logic that you want, and give more examples please?

#3

Hi tfox_TD, thanks for replying back.

Yes, I had only want items to be returned based off from the given conditions.
Eg.

  • conditions = {'menuA':['a200']} --> no items will be returned…
  • conditions = {'menuA':['a100', 'a200']} --> item04 will be returned…
  • conditions = {'menuA':['a100'], 'menuB':['b200']} --> item01 and item03 will be returned

To sum it up (hopefully I did not confuse you or myself), I am trying to achieve - show me the items that contains the conditions (menuA/a100 + menuB/b200 etc…)

#4

Check your third example. item03 shouldn’t be returned since the value for menuB also contains b100

So, assuming I’m right in saying that, you’re VERY close to the answer. The problem is in how you set all_conditions_met inside your loop.

First, you probably shouldn’t be using the bitwise & when you mean the logical and. Unfortunately there’s not an &= for logical and, so it needs to be expanded to val = val and condition

Then, bool(someSet & otherSet) returns True when there is at least one item in common with the sets, and that doesn’t seem to be what you’re looking for. It looks more like you’re checking if they’re the same, so you should use someSet == otherSet

So try it like this:
all_conditions_met = all_conditions_met and (set(mv[condition]) == set(conditions[condition]))

#5

@Teh_Ki Something like this? - Super rough…


rules = {
"a": {"menuA": ["a100"], "menuB":["a200", "a100"]},
"b": {"menuA": ["a100", "a200"]},
"c": {"menuD": ["b200"]}
}

condition = {"menuA": ["a100", "a200"]}

keys = []

for vals in rules.values():
  if vals.keys() == condition.keys():

    for c_key, c_value in condition.items():

      for r_key, r_value in rules.items():

        if c_key in r_value.keys():

          v_count = 0

          for value in c_value:
            if value in r_value[c_key]:
              v_count += 1

          if v_count == len(c_value):
            keys.append(r_key)

print set(keys)
# set(['b'])

#6

Hi all, sorry for the late response.

@tfox_TD:
Can’t really remember that reason I used &=, either I have seen it somewhere or I had thought it will work…

@chalk
Thank you for the code. While I did not get to try out your code, the number of indentation is starting to hurt my eyes a little (no offense here)…

Even so, I have a question on hand.
Prior to what tfox_TD has mentioned:

Then, bool(someSet & otherSet) returns True when there is at least one item in common with the sets, and that doesn’t seem to be what you’re looking for. It looks more like you’re checking if they’re the same, so you should use someSet == otherSet

My current objective is to make my method works something akin to a tagging system. Should I show all items as long as it fits one of the condition?

#7

So in Gmail, for instance, an email can have multiple tags. And if you search any single one of them, you get any email that has that tag. But, of course, the emails returned could have other tags.

So, the examples you gave led me to believe you want someSet == otherSet, but a tagging system is definitely a bool(someSet & otherSet)

So now that you have code that’s close to working, but you’ve got a choice of two behaviors, put a big “TODO” comment around that code, and go ahead and keep working on the rest of it. Once you get something that’s closer to user-ready, it’ll be much easier to play with the logic and figure out exactly what you want

#8

Hi tfox_TD,

wondering if you could give me an overview if the following of what I did will be more accurate?

for m in my_items:
    for mk, mv in m.items():
        # OR
        res1 = all(any(x in mv.get(kc, []) for x in vc) for kc, vc in conditions.items())

        all_conditions_met = len(conditions) == len(mv) #False
        for cond_key, cond_val in conditions.items():
            if condition in mv:
                # AND
                res2 = all_conditions_met and (set(mv[condition]) == set(conditions[condition]))
                all_conditions_met = bool(res1 or res2)

            else:
                all_conditions_met = False
                break
        if all_conditions_met:
            result.append(mk)
#9

First, your code doesn’t run. if condition in mv fails because condition isn’t defined.

I don’t think it’ll be more accurate. You’re combining both behaviors, instead of choosing between them.
I’m suggesting something more like this:

result = []
for item in my_items:
	for keyItem, valItem in item.iteritems():
		# TODO: I have two choices for my tagging behavior. One of these should be correct, but I'm not sure which one
		if True: # I can change this manually to easily switch between behaviors
			# This choice checks that all condition values exist somewhere in my item
			res = [any(x in valItem.get(keyCond, []) for x in valCond) for keyCond, valCond in conditions.iteritems()]
		else:
			# This choice checks that all condition menu values match exactly
			res = [set(valItem.get(keyCond, [])) == set(valCond) for keyCond, valCond in conditions.iteritems()]
		#print zip(conditions.keys(), res) # print which menu items match, just for debugging
		if all(res):
			result.append(keyItem)
print result

Once you figure out which of the two behaviors you want, you can just remove the other one from the code

#10

This should handle your conditions and the code should be simpler.

def resolve_condition(condition, rules):

  keys = []

  for key, value in rules.items():
    if condition.keys() == value.keys():

      for item in condition.items():
        if item in value.items():

          keys.append(key)

  return list(set(keys)) or None

rules = {
  'item01' : {'menuA': ['a100'], 'menuB': ['b400']},
  'item02' : {'menuA': ['a200'], 'menuB': ['b200'], 'menuC' : ['c100']},
  'item03' : {'menuA': ['a100'], 'menuB': ['b100', 'b200']},
  'item04' : {'menuA': ['a100', 'a200']},
  'item05' : {'menuB': ['b100'], 'menuC': ['c100']}
}

conditions = [
  {'menuA':['a200']},
  {'menuA':['a100', 'a200']},
  {'menuA':['a100'], 'menuB':['b200']} 
]

for each in conditions:
  print resolve_condition(each, rules)

# None
# ['item04']
# ['item03', 'item01']

This was a little bit of a logic bomb because what I think you want is if any match regardless of item i.e. partially exact. Any matched values of matched keys.