1

I have a list of dictionaries with all of them having the same keys such as

input = [                  
    {                      
        "animal": "Tiger"
        "country": "US",   
        "color": "yellow-black"   
    },                     
    {                      
        "animal": "Dog"
        "country": "UK",   
        "color": "brown"       
    },                     
    {                      
        "animal": "Tiger"
        "country": "Nepal",   
        "color": "yellow-black"     
    }                                                              
]  

I would like to create a new dictionary where the ones which share the same value for a specified key (here animal) are grouped together. While grouping them I would like to remove the 'animal' key from the initial dictionaries. For the given example it would like this

output = {
        "Tiger":
        [{                      
            "country": "US",   
            "color": "yellow-black"   
        }, 
        {                      
            "animal": "Tiger"
            "country": "Nepal",   
            "color": "yellow-black"     
        }],
        "Dog": [
        {                      
            "country": "UK",   
            "color": "brown"       
        }]                     
}                                                                  

I achieve this with the code below, but I am quite sure that there must be a more elegant approach to this. Is it possible to write this as a one-liner?

grouped = dict((k, list(g)) for k, g in itertools.groupby(input, key=lambda x:x['animal'])) 
for k, g in grouped.items():                                                                  
    for i in range(len(grouped)):                                                             
        del g[i]['animal']  
4
  • 2
    Do you really want "animal": "Tiger" in the output? As in, it's a repeated item that you've categorised under the primary key Commented Mar 1, 2019 at 19:01
  • Your input dict is missing a few commas. I also notice that when I run your groupby approach, the "Tiger" list only has the nepalese tiger; the US tiger is not present. For this reason I don't think the groupby approach is a practical one. Commented Mar 1, 2019 at 19:03
  • 1
    itertools.,groupby only groups consecutive keys - your tigers are one other animal appart so they wont be grouped Commented Mar 1, 2019 at 19:06
  • Sorry for the typos, I will make the corrections. @PatrickArtner thank you for the insight, learnt something new today Commented Mar 1, 2019 at 19:13

3 Answers 3

4

The easiest way is probably using a defaultdict. I'm assuming you actually want to drop the "animal" tag in the output, since you also have missing commas in the input so likely a typo.

from collections import defaultdict

output = defaultdict(list)

inp = [                  
    {                      
        "animal": "Tiger",
        "country": "US",   
        "color": "yellow-black"   
    },                     
    {                      
        "animal": "Dog",
        "country": "UK",   
        "color": "brown"       
    },                     
    {                      
        "animal": "Tiger",
        "country": "Nepal",   
        "color": "yellow-black"     
    }                                                              
]  

for item in inp:
    output[item['animal']].append({k: v for k, v in item.items() 
                                   if k != 'animal'})

Depending on how many key/value pairs you have in your dictionary, it may be quicker to simply remove the key from the dictionary, rather than use a dictionary comprehension to rebuild a dictionary excluding that key. For a sample this size, it really doesn't matter for speed, and it doesn't risk altering your initial data.

Sign up to request clarification or add additional context in comments.

7 Comments

This works great. Just out of pure curiosity, would you happen how I can shorten my code above assuming that I am using the grouped functionality?
@Tony.H you want a solution using itertools.groupby? I can have a go, since I don't mind trying to correct initial code rather than just give another approach, but I want to be sure that's what you're asking me to do.
yes I would be very grateful for that. I like your solution but I was just curious how it would work with itertools.groupby
@PatrickArtner so I see :) I need to get my head around the double lambda because that wasn't what I thought I was aiming at (but then I wasn't finished in my attempt and I didn't try get a 1-liner on my first-pass so maybe it would crop up)
@PatrickArtner Oh, I know it's not the cleanest approach, but you did answer the subsidiary question the OP had for me on how to make it work, so I've upvoted :)
|
2

This would be your fixed attempt - but it needs pre-sorting and is less effective then the defaultdict:

# fixed data
data = [ { "animal": "Tiger",  "country": "US",    "color": "yellow-black" },
         {  "animal": "Dog",   "country": "UK",    "color": "brown" }, 
         {  "animal": "Tiger", "country": "Nepal", "color": "yellow-black" } ] 

from itertools import groupby

# groupby needs sorted keys if you want to group them together 
grouped = dict((k, list(g)) for k, g in groupby(sorted(data,key=lambda x:x["animal"]), 
                                                key=lambda x:x['animal'])) 

# delete the animal key
for k in grouped:
    for inner in grouped[k]:
        del inner["animal"]

print(grouped)

Output:

{  'Dog': [{'country': 'UK', 'color': 'brown'}], 
 'Tiger': [{'country': 'US', 'color': 'yellow-black'}, 
           {'country': 'Nepal', 'color': 'yellow-black'}]}

Doku:

Make an iterator that returns consecutive keys and groups from the iterable. The key is a function computing a key value for each element. If not specified or is None, key defaults to an identity function and returns the element unchanged. Generally, the iterable needs to already be sorted on the same key function.

Comments

0

Not be one liner but defaultdict is the one to go with

from collections import defaultdict
d=defaultdict(list)
for i in input:
    d[i['animal']].append({k:v for k,v in  i.items() if k!='animal' })

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.