Transforming a dict to another structure in Python - python

I have a dict like below:
{'activity_count': [10, 11, 12], 'type': ['all', 'paper', 'fpy']}
I want to transform this dict into this form:
{'all': {'activity_count': 10}, 'paper': {'activity_count': 11}, 'fpy': {'activity_count': 12}}
How can I solve this?
So far I tried this solution,
dic={"activity_count":[10,11,12],"type":["all","paper","fpy"]}
in={}
i=0
for val in dic['type']:
for v in dic['activity_count']:
if i== dic['activity_count'].index(v):
temp={}
temp['activity_count']=v
fin[val]=temp
i+=1
It works as I expected, but it looks very ineffective way to achieve this task. Is there a way to solve this problem?

Here a try, here zip is used to get values from both lists and to assign each:
d = {'activity_count': [10, 11, 12], 'type': ['all', 'paper', 'fpy']}
nd = {j:{'activity_count':i} for i, j in zip(d['activity_count'], d['type'])}
print(nd)

I would go for zip and dict comprehension:
test = {'activity_count': [10, 11, 12], 'type': ['all', 'paper', 'fpy']}
solution = {key:{'activity_count':value} for value, key in zip(test["activity_count"],test["type"])}
Explanation: The zip of your two list groups the elements of the two list by with identical index. So it will convert your lists to a generator where the values are like this: [(10, 'all'), (11, 'paper'), (12, 'fpy')]. But the generator is lazy evaluated, so the tuples are only processed, when the dict comprehension asks for them, this saves memory.
The dict comprehension just iterates over this generator and puts the second element as key and the first one as value.

You could try this dictionary comprehension using enumerate:
dictionary = {'activity_count': [10, 11, 12], 'type': ['all', 'paper', 'fpy']}
{e:{"activity_count":dictionary.get("activity_count")[c]} for c,e in enumerate(dictionary.get("type"))}

Related

How to make a Custom Sorting Function for Dictionary Key Values?

I have a dictionary whose key values are kind of like this,
CC-1A
CC-1B
CC-1C
CC-3A
CC-3B
CC-5A
CC-7A
CC-7B
CC-7D
SS-1A
SS-1B
SS-1C
SS-3A
SS-3B
SS-5A
SS-5B
lst = ['CC-1A', 'CC-1B', 'CC-1C', 'CC-3A', 'CC-3B', 'CC-5A', 'CC-7A', 'CC-7B',
'CC-7D', 'SS-1A', 'SS-1B', 'SS-1C', 'SS-3A', 'SS-3B', 'SS-5A', 'SS-5B']
d = dict.fromkeys(lst)
^Not exactly in this order, but in fact they are all randomly placed in the dictionary as key values.
Now, I want to sort them. If I use the built in function to sort the dictionary, it sorts all the key values according to the order given above.
However, I want the dictionary to be first sorted based upon the values after the - sign (i.e. 1A, 1B, 1C etc.) and then based upon the first two characters.
So, for the values given above, following would be my sorted list,
CC-1A
CC-1B
CC-1C
SS-1A
SS-1B
SS-1C
CC-3A
CC-3B
SS-3A
SS-3B
CC-5A
and so on
First, sorting is done based upon the "4th" character in the keys. (that is, 1, 3, etc.)
Then sorting is done based upon the last character (i.e. A, B etc.)
Then sorting is done based upon the first two characters of the keys (i.e. CC, SS etc.)
Is there any way to achieve this?
Your "wanted" and your sorting description deviate.
Your "wanted" can be achieved by
di = {"CC-1A":"value1","CC-1A":"value2","CC-1B":"value3",
"CC-1C":"value4","CC-3A":"value5","CC-3B":"value6",
"CC-5A":"value7","CC-7A":"value8","CC-7B":"value9",
"CC-7D":"value0","SS-1A":"value11","SS-1B":"value12",
"SS-1C":"value13","SS-3A":"value14","SS-3B":"value15",
"SS-5A":"value16","SS-5B":"value17"}
print(*((v,di[v]) for v in sorted(di, key= lambda x: (x[3], x[:2], x[4]) )),
sep="\n")
to get
('CC-1A', 'value2')
('CC-1B', 'value3')
('CC-1C', 'value4')
('SS-1A', 'value11')
('SS-1B', 'value12')
('SS-1C', 'value13')
('CC-3A', 'value5')
('CC-3B', 'value6')
('SS-3A', 'value14')
('SS-3B', 'value15')
('CC-5A', 'value7')
('SS-5A', 'value16')
('SS-5B', 'value17')
('CC-7A', 'value8')
('CC-7B', 'value9')
('CC-7D', 'value0')
which sorts by number (Pos 4 - (1based)), Start (Pos 1+2 (1based)) then letter (Pos 5 (1based))
but that conflicts with
First, sorting is done based upon the "4th" character in the keys.
(that is, 1, 3, etc.)
Then sorting is done based upon the last character (i.e. A, B etc.)
Then sorting is done based upon the first two characters of the keys
(i.e. CC, SS etc.)
One suggestion is to use a nested dictionary, so instead of:
my_dict = {'CC-1A1': 2,
'CC-1A2': 3,
'CC-1B': 1,
'CC-1C': 5,
'SS-1A': 33,
'SS-1B': 23,
'SS-1C': 31,
'CC-3A': 55,
'CC-3B': 222,
}
you would have something like:
my_dict = {'CC': {'1A1': 2, '1A2': 3, '1B': 1, '1C': 5, '3A': 55, '3B': 222},
'SS': {'1A': 33, '1B': 22, '1C': 31}
}
which would allow you to sort first based on the leading number/characters and then by group. (Actually I think you want this concept reversed based on your question).
Then you can create two lists with your sorted keys/values by doing something like:
top_keys = sorted(my_dict)
keys_sorted = []
values_sorted = []
for key in top_keys:
keys_sorted.append([f"{key}-{k}" for k in my_dict[key].keys()])
values_sorted.append([v for v in my_dict[key].values()])
flat_keys = [key for sublist in keys_sorted for key in sublist]
flat_values = [value for sublist in values_sorted for value in sublist]
Otherwise, you'd have to implement a custom sorting algorithm based first the characters after the - and subsequently on the initial characters.
You can write a function to build a sorting key that will make the required decomposition of the key strings and return a tuple to sort by. Then use that function as the key= parameter of the sorted function:
D = {'CC-1A': 0, 'CC-1B': 1, 'CC-1C': 2, 'CC-3A': 3, 'CC-3B': 4,
'CC-5A': 5, 'CC-7A': 6, 'CC-7B': 7, 'CC-7D': 8, 'SS-1A': 9,
'SS-1B': 10, 'SS-1C': 11, 'SS-3A': 12, 'SS-3B': 13, 'SS-5A': 14,
'SS-5B': 15}
def sortKey(s):
L,R = s.split("-",1)
return (R[:-1],L)
D={k:D[k] for k in sorted(D.keys(),key=sortKey)}
print(D)
{'CC-1A': 0,
'CC-1B': 1,
'CC-1C': 2,
'SS-1A': 9,
'SS-1B': 10,
'SS-1C': 11,
'CC-3A': 3,
'CC-3B': 4,
'SS-3A': 12,
'SS-3B': 13,
'CC-5A': 5,
'SS-5A': 14,
'SS-5B': 15,
'CC-7A': 6,
'CC-7B': 7,
'CC-7D': 8}
If you expect the numbers to eventually go beyond 9 and want a numerical order, then right justify the R part in the tuple: e.g. return (R[:-1].rjust(10),L)
You could use a custom function that implements your rule as sorting key:
def get_order(tpl):
s = tpl[0].split('-')
return (s[1][0], s[0], s[1][1])
out = dict(sorted(d.items(), key=get_order))
Output:
{'CC-1A': None, 'CC-1B': None, 'CC-1C': None, 'SS-1A': None, 'SS-1B': None, 'SS-1C': None, 'CC-3A': None, 'CC-3B': None, 'SS-3A': None, 'SS-3B': None, 'CC-5A': None, 'SS-5A': None, 'SS-5B': None, 'CC-7A': None, 'CC-7B': None, 'CC-7D': None}

Create arrays of arrays and use dictionary

I have two lists:
x = ['1','5','X','9']
y = ['X','9','9P']
which I have combined like this:
z = [x,y]
print(z)
So, "z" is a list of two lists and it looks like this:
[['1', '5', 'X', '9'], ['X', '9', '9P']]
I have this dictionary:
delqType = {
'1' : 0,
'2' : 3,
'3' : 4,
'4' : 5,
'5' : 6,
'7' : 19,
'8' : 21,
'8A' : 20,
'8P' : 16,
'9' : 22,
'9B' : 18,
'9P' : 15,
'UR' : 23,
'X' : -99999
}
I want to apply the dictionary delqType to the list "z" in order to get this new list (called "t") that looks like:
[[0, 6, -99999, 22], [-99999, 22, 15]]
I have tried this code:
t = []
for i in range(len(z)):
for j in range(len(z[i])):
t.append(delqType[z[i][j]])
next
But I get one list (and not a list of two lists as I want):
[0, 6, -99999, 22, -99999, 22, 15]
How can I get a list of two lists containing the elements after they have been transformed using the dictionary?
You can use nested list comprehension.
t = [[delqType[i] for i in list_i] for list_i in z]
print(t)
[[0, 6, -99999, 22], [-99999, 22, 15]]
You should created the nested lists in t, one for each nested list of z. In addition it will be simpler to iterate the items instead of the indices:
t = []
for sub in z:
t.append([])
for item in sub:
t[-1].append(delqType[item])
Another nice way is to use the itemgetter operator to map the list of keys to the list of values:
from operator import itemgetter
t = [itemgetter(*sub)(delqType) for sub in z]
As you want to get a 2 dimensions list, you need to append a list inside your first for loop.
(But also, please use the for loop to directly extract values, instead of using indices !)
So, this would look like:
t = []
for sublist in z:
new_sublist = []
for key in sublist:
newlist.append(...)
t.append(new_sublist)
I let to you to fill the internals of the deepest for loop, as an exercise, to ensure you got it correctly :-)
Side note: your code sample is not correct, as there is no next keyword in Python!! Please be sure to provide the exact code you executed.

Adapting the same sorting order from a list

I have a list containing numbers and one containing users. The first element of the first list belongs to the first element of the second list and so on. I then want to sort the number list using sort() and want the usernames to still match with the numbers they had before.
from:
users = ["max", "david", "freddy"]
numbers = [9, 3, 10]
to:
users = ["david", "max", "freddy"]
numbers = [3, 9, 10]
You could use a list comprehension along with zip and sorted:
>>> users = ["max", "david", "Freddy"]
>>> numbers = [9, 3, 10]
>>> sorted_users = [u for _, u in sorted(zip(numbers, users)]
>>> sorted_users
['david', 'max', 'freddy']
You can zip the two into one list, sort based on the number, then unzip them:
users = ["max", "david", "freddy"]
numbers = [9, 3, 10]
zipped = sorted(zip(numbers, users), key=lambda x: x[0])
numbers, users = zip(*zipped)
I would suggest grouping your values together in a list of tuples or dictionaries:
data = [
('max', 9),
('david', 3),
('freddy', 10)
]
and then you can use custom sorting to sort this list:
data.sort(key=lambda x: x[1])
This way your data is stored in a way which keeps their relationship.
To use dictionaries instead requires this simple change:
data = [
{'name': 'max', 'number': 9},
{'name': 'david', 'number': 3},
{'name': 'freddy', 'number': 10}
]
and then change your sort code to
data.sort(key=lambda x: x['number'])
Using the right combination of zip and map, you can do this in one line:
users = ["max", "david", "freddy"]
numbers = [9, 3, 10]
numbers,users = map(list,zip(*sorted(zip(numbers,users))))
print(users) # ['david', 'max', 'freddy']
print(numbers) # [3, 9, 10]
You could try a dictionary for user numbers and user names
users = ["max", "david", "freddy"]
numbers = [9, 3, 10]
dictionary = {users[i]: numbers[i] for i in range(len(numbers))}
{'max': 9, 'david': 3, 'freddy': 10}
and then sort the dictionary and convert back to lists. \

Making list of dictionary values that correspond to unordered key list

I have a the dictionary of
animals = {'cat': 24, 'dog': 21, 'bird': 16, 'fish': 11}
and a list that matches animal.keys(), except for the order:
messy_animals = ['bird', 'dog', 'fish', 'cat']
what would be the easiest way to create a list that contains animals.values() in the same order as the messy_animals list?
So the expected output would be:
messy_animals_counts = [16, 21, 11, 24]
I hope it is clear what I want to accomplish.
Thank you and have a nice weekend.
You can do this with a list comprehension:
messy_animal_counts = [animals[m] for m in messy_animals]
If you don't know that everything in messy_animals is also in the dict, you could use get instead.
messy_animal_counts = [animals.get(m) for m in messy_animals]
If you provide a second parameter to the get method, you can specify a default (instead of None).
This method has time complexity O(N).
I found a solution:
for x in messy_a:
for k,v in animals.items():
if x == k:
messy_a_counts.append(v)
print(messy_a_counts)
Output:
[16, 21, 11, 24]
you could use the built-in function map with dict.get:
list(map(animals.get, messy_animals))
output:
[16, 21, 11, 24]

Making a list of dictionaries from a list of tuples

I'm a Python newbie. As a fun exercise, I thought I would create a list of dictionaries from a list of tuples. Little did I know I would bang my head against the wall for hours.
boys = [("Joe", 7, 125), ("Sam", 8, 130), ("Jake", 9, 225)]
keys = ("Name","Height","Weight")
boyz = []
for x in boys:
z = dict(zip(keys,boys[x]))
boyz.append(z)
print(boyz)
When the x in "boys[x]" is replaced with a integer, it works great, but replacing it with a variable within the for loop won't work. WHY?? I'd love an answer to that specifically. But also if there's a more concise to write this whole thing, please let me know.
In each iteration of the for x in boys loop, x will be the value of the next tuple in the list. It is not an integer you can use as an index. Use x instead of boys[x] in the zip to get the result you want.
for x in boys:
z = dict(zip(keys,x))
boyz.append(z)
You are using boys[x] instead of x.
This raises the error:
TypeError: list indices must be integers, not tuple
Here is your edited code:
boys = [("Joe", 7, 125), ("Sam", 8, 130), ("Jake", 9, 225)]
keys = ("Name","Height","Weight")
boyz = []
for x in boys:
z = dict(zip(keys,x))
boyz.append(z)
print(boyz)
This runs as:
>>> boys = [("Joe", 7, 125), ("Sam", 8, 130), ("Jake", 9, 225)]
>>> keys = ("Name","Height","Weight")
>>> boyz = []
>>> for x in boys:
... z = dict(zip(keys,x))
... boyz.append(z)
...
>>> print(boyz)
[{'Name': 'Joe', 'Weight': 125, 'Height': 7}, {'Name': 'Sam', 'Weight': 130, 'Height': 8}, {'Name': 'Jake', 'Weight': 225, 'Height': 9}]
>>>
boys is a list. lists only support indices that are integers but you're passing in a tuple. You'll want to use x as the tuple.
Ultimately, your goal is to get an iterable of keys and values to pass in to zip.
dict(zip(keys, values))
A more concise version, as you asked, can be achieved using list comprehension.
boyz = [dict(zip(keys, boy)) for boy in boys]
Generally, when you see the pattern of creating an empty list, iterating over some iterable and appending its values after map / filtering, you can use a list comprehension instead.
This:
new_list = []
for item in iterable:
if condition(item):
new_value = mapping(item)
new_list.append(new_value)
Is equivalent to:
new_list = [mapping(item) for item in iterable if condition(item)]

Categories