-2

Suppose I have two objects in Python (I'll keep it simple)

A = { "a" : { "b": 3, "c": 2 } }

B = { "a" : { "b": 7, "c": 4, "d": "howdy" } }

If I want to use A as a template for B, what's the best way to "trim" object B so that it will look like:

B = { "a" : { "b": 7, "c": 4 } }

In practice, the objects will be much more complex (simple data types but multi-level, lists, dict, etc).

edit: removed nonsensical 'Obj' to fix syntax

rottyguy
  • 55
  • 1
  • 9
  • 5
    By objects do you meant Dictionary? Is it guarantee that obj B has the same keys as Obj B? What have you tried so far? – MooingRawr Jan 23 '17 at 20:19
  • 2
    I'm fearing not answering your question if I answer using your simple example. those aren't objects, they're dictionaries, and your declaration is not correct. – Jean-François Fabre Jan 23 '17 at 20:19
  • create `Obj C` and copy elements of `Obj B` using keys from `Obj A` - after then put `Obj C` in place of `Obj B` – furas Jan 23 '17 at 20:20
  • Do you have to handle arbitrary levels of nesting, or is it always just 2 levels? – Barmar Jan 23 '17 at 20:26
  • @Jean-FrançoisFabre of *course* they are objects. *Everything* is an object in Python :) – juanpa.arrivillaga Jan 23 '17 at 20:27
  • Are you sure you are using Python. Your code would be a `SyntaxError`. – juanpa.arrivillaga Jan 23 '17 at 20:28
  • 1
    @juanpa.arrivillaga of course, but I meant that this is more javascript syntax than python. – Jean-François Fabre Jan 23 '17 at 20:34
  • Yes, sorry. Python is not my first language. We can assume they'll be dictionaries of arbitrary make up. The template is defined via xml so I would need to construct the template dictionary and then apply it to B (where B is constructed as a result of some function). In short, I'm looking to filter out certain fields/keys based on some template. – rottyguy Jan 23 '17 at 20:35

1 Answers1

0

You can use a recursive function for this: First, check whether the type of the template and the original are the same. If so, you have to do different things for different types. E.g., for lists, you could reduce the original list to the length of the template list, and for dictionaries preserve only the keys from the template. In both cases, remember to recursively call the trim function again for the elements contained in those lists or dictionaries.

def trim(template, original):
    if type(template) != type(original):
        raise Exception("Types don't match")
    if isinstance(template, dict):
        # iterate keys and apply trim to values from orig dictionary
        return {k: trim(v, original[k]) for k, v in template.items()}
    elif isinstance(template, list):
        # zip lists and apply trim to pairs of templ and orig elements
        return [trim(t, o) for t, o in zip(template, original)]
    else:
        # atom: just return the original value
        return original

Example (note that I also changed the value of "c" to a list):

>>> A = { "a" : { "b": 3, "c": [4,5] } }
>>> B = { "a" : { "b": 7, "c": [8,9,10], "d": "howdy" } }
>>> trim(A, B)
{'a': {'b': 7, 'c': [8, 9]}}
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • Ok thanks. It's actually simpler in that I just need to restrict 'keys'. I may consider going the other way-- black list, instead of white list. That is load my template as A = { "a" : [ "d" ] } to signify that a.d (again, sorry for not using proper python syntax) should be excluded. – rottyguy Jan 23 '17 at 20:51
  • @rottyguy How is "restricting" keys simpler? How would you generalize that to, e.g., lists? – tobias_k Jan 23 '17 at 20:59
  • not sure I fully understand your question. If I'm just restricting on keys (in B), my template (A) could just contain the key names. Do you disagree (if so, can you provide an example?) – rottyguy Jan 23 '17 at 22:03
  • @rottyguy I can not disagree because I do not understand how your "template" is supposed to look now. In your question, your template was just another dict with the same structure and just those keys to retain. Then you changed that to a dict of keys to remove (although in your example, you want to retain `a`, but remove `d`). And now you want to have a dict with keys to retain again, but only the keys? What is wrong with your original question for which I provided a working solution? – tobias_k Jan 23 '17 at 22:15
  • ah, sorry for the confusion. I started out with a "whitelist" approach which was to make the template mirror the input dictionaries (minus the restrictions). You resolved by walking all elements in both list and pulling the keys that are in B and not A. I'm now wondering (aloud) whether approaching it from a "blacklist" pov (hold the elements in A that should be restricted in B) would be more elegant/easier? A = { "a" : ["d"] } just means to restrict key at B["a"]["d"]. thus A= { "a" : ["c","d"] } would leave B = { "a" : { "b" : 7 } } in your example. Hope that makes sense. – rottyguy Jan 23 '17 at 22:27
  • @rottyguy So what if there is another dict on the top-level of `B` that you want to remove entirely. Say `B = {'a' = {...}, 'b' = {...}}`. How would `A` look then? `A = {'a': ['c', 'd'], 'b': None}` maybe? note that here the `'a'` means "keep 'a', but drop the values in the list", while `'b'` means "drop 'b' entirely". Or what if there is another dict in the `['c', 'd']` list from which you only want to remove a few keys, but not the entire dict? To me, this is highly confusing and inconsistent, and does not seem "easier" in any way. – tobias_k Jan 23 '17 at 23:01
  • I was thinking that the 'values' in A were superfluous (in my example) and that I only need the keys. Maybe A, as a template, is just all 'list's that hold the key names. So for your example: A = [ 'a' : ['c', 'd'], 'b'] to remove B['a']['c'], B['a']['d], and B['b']. That way, I only need to iter on A. I don't expect there to be a lot of elements that will be filtered in the general use case which is why I think black listing might be more efficient. thoughts? – rottyguy Jan 24 '17 at 10:54