assign values based on a dict - python

I have a list1 with names:
["SAM","TOM","LOUIS"]
And I have a dict1 like this (where in the list of values there are no repeated names:
{"NICE": ["SAM", "MAIK", "CARL", "LAURA", "MARTH"],
"BAD": ["LOUIS", "TOM", "KEVIN"],
"GOOD": ["BILL", "JEN", "ALEX"]}
How could I iterate throught the list1 so that if any of the names appear in any of the lists of the dict1 it assigns the corresponding key of the dict?
I am looking forward to generate the following output:
["NICE","BAD","BAD"]
which would correspond to the keys of the values that appear in the list : SAM, TOM , LOUIS .
This is what I thought about:
lista=[]
for k,v in dict1:
for values in arr1:
if values in v:
lista.append(v)
lista
However not sure how to iterate over the different v, how can I get the desired output in an efficient manner?

You can create an intermediate dict that maps names to their keys in dict1:
categories = {name: category for category, names in dict1.items() for name in names}
so that you can map the names in list1 to their respective keys efficiently with:
[categories[name] for name in list1]
which returns:
['NICE', 'BAD', 'BAD']

I think you need the items() function for dictionaries here. For each name, iterate over all of the dictionary pairs and stop when a match is found, adding the corresponding adjective to your list.
lista = []
for name in list1:
for adjective, names in dict1.items():
if name in names:
lista.append(adjective)
break
return lista

There are 2 ways to achieve your result.
The way you intended was to use dict1.items(). Unfortunately, that way is computationally slow, so for the sake of completeness I'll add the the more efficient way:
# First, convert the dict to a different representation:
from itertools import chain, repeat
# Change {k: [v1,v2], k2: [v3,v4]} to {v1: k, v2: k, v3: k2, v4: k2}
name_to_adjective = dict(chain.from_iterable(zip(v, repeat(k)) for k, v in a.items()))
Name_to_adjective is now equal to this:
{'ALEX': 'GOOD',
'BILL': 'GOOD',
'CARL': 'NICE',
'JEN': 'GOOD',
'KEVIN': 'BAD',
'LAURA': 'NICE',
'LOUIS': 'BAD',
'MAIK': 'NICE',
'MARTH': 'NICE',
'SAM': 'NICE',
'TOM': 'BAD'}
Then, you can get your result in one run:
result = [name_to_adjective[name] for name in list1]

I believe the following will give you your desired output:
L = ["SAM","TOM","LOUIS"]
D = {"NICE": ["SAM", "MAIK", "CARL", "LAURA", "MARTH"]
, "BAD": ["LOUIS", "TOM", "KEVIN"]
, "GOOD": ["BILL", "JEN", "ALEX"]}
lista = []
for key in D.keys():
for name in L:
if name in D[key]:
lista.append(key)
print(lista)
The D.keys() part gives you a list of the keys in a friendly manner (e.g, ["NICE", "BAD", "GOOD"]).
You iterate over this and then look for each name from L (in order) in the dictionary.
This is not the most efficient way to do this, however, it's a more straightforward approach.

Related

Make 2d list from another 2d list with loops and if condition

Hello there i m very new in python and i think like c# and i cant do some things.
I have this list for ex.
data = [["Bob","Algebra",5],["Bob","History",4],["Bob","Physics",7],["Bob","Astronomy",5],["Allen","Algebra",5],["Allen","History",4],["Allen","Physics",7],["Mary","Algebra",5],["Mary","History",3],["Mary","Physics",7],["Mary","Astronomy",8]
]
How i make this output from that:
MathsPerStudent = [["Bob","Algebra","History","Physics","Astronomy"]
["Allen","Algebra","History","Physics"]
["Mary","Algebra","History","Physics","Astronomy"]]
As i see i cant have
MathsPerStudents=[[]]
for i in data:
for j in MathsPerStudents:
if data[i][0] == j[0]
MathsPerStudents.append(j[0][i])
and with some way to fill the MathPerStudents correctly. As i can see data[i] cant compare with j[i] because the one is list the other is int from element. The point is in for loop how could i use (i) as index in a list like data list.
Using setdefault
Ex:
data = [["Bob","Algebra",5],["Bob","History",4],["Bob","Physics",7],["Bob","Astronomy",5],["Allen","Algebra",5],["Allen","History",4],["Allen","Physics",7],["Mary","Algebra",5],["Mary","History",3],["Mary","Physics",7],["Mary","Astronomy",8]]
result = {}
for k, sub, _ in data:
result.setdefault(k, []).append(sub) #Set first element as key and append subject as value
print([[k] + v for k, v in result.items()]) #Form required list
Output:
[['Bob', 'Algebra', 'History', 'Physics', 'Astronomy'],
['Allen', 'Algebra', 'History', 'Physics'],
['Mary', 'Algebra', 'History', 'Physics', 'Astronomy']]
You can do something like this (not a one-liner, but maybe easier to understand):
MathsPerStudent = {}
for name, subject, grade in data:
if name not in MathsPerStudent:
MathsPerStudent[name] = []
MathsPerStudent[name].append(subject)
This gives you a dictionary:
>>> from pprint import pprint
>>> pprint(MathsPerStudent)
{'Allen': ['Algebra', 'History', 'Physics'],
'Bob': ['Algebra', 'History', 'Physics', 'Astronomy'],
'Mary': ['Algebra', 'History', 'Physics', 'Astronomy']}
If you want you can then convert it to list form, but probably the dictionary would be easier to work with for whatever you're trying to achieve:
MathsPerStudentList = [[name] + subjects for name, subjects in MathsPerStudent.items()]
Here the final list:
>>> pprint(MathsPerStudentList)
[['Bob', 'Algebra', 'History', 'Physics', 'Astronomy'],
['Allen', 'Algebra', 'History', 'Physics'],
['Mary', 'Algebra', 'History', 'Physics', 'Astronomy']]
You could use itertools.groupby if your list of lists is ordered by names:
import itertools
data = [["Bob","Algebra",5],["Bob","History",4],["Bob","Physics",7],["Bob","Astronomy",5],["Allen","Algebra",5],["Allen","History",4],["Allen","Physics",7],["Mary","Algebra",5],["Mary","History",3],["Mary","Physics",7],["Mary","Astronomy",8]]
output = []
for student, classes in itertools.groupby(data, key=lambda x: x[0]):
output.append([student] + [entry[1] for entry in classes])
print(output)
That's exactly what defaultdict can be used for:
from collections import defaultdict
subjects_by_name = defaultdict(list) # if element is missing in the dict, it will be a list
for name, subject, _ in data:
subjects_by_name[name].append(subject)
MathsPerStudents = [[name, *subjects] for name, subjects in subjects_by_name.items()] # make a list of lists out of a dict
As you can see it is similar to #Rakesh's answer. As #pallgeuer pointed out, it would be easier to work with a dict instead of converting it to a list of lists. Get used to dicts in python as soon as you can!

How to remove short strings from a list which is a value of a dict

I want to make a function called remove_short_synonyms() which is passed a dict
as a parameter. The keys of the parameter dict are words and the
corresponding values are lists of synonyms. The function removes all the
synonyms which have less than 7 characters from each corresponding list
of synonyms.
If this is the dict:
synonyms_dict = {'beautiful': ['pretty', 'lovely', 'handsome', 'dazzling', 'splendid', 'magnificent']}
How can I get this as the output?
{'beautiful': ['dazzling', 'handsome', 'magnificent', 'splendid']}
I think your question is more proper to be titled as Remove values from a list instead of dict.
You can use remove, del or pop to remove element in a python list.
Difference between del, remove and pop on lists
Or in a more pythonic way, i think, is
dict['beautiful'] = [item for item in dict['beautiful'] if len(item)>=7]
Make use of dict comprehension and list comprehension.
synonyms_dict = {'beautiful' : ['pretty', 'lovely', 'handsome', 'dazzling', 'splendid', 'magnificent']}
synonyms_dict = {k:[v1 for v1 in v if len(v1) >= 7] for k, v in synonyms_dict.items()}
print(synonyms_dict)
# {'beautiful': ['handsome', 'dazzling', 'splendid', 'magnificent']}
​
Assuming you have python>=3.x, a more readable solution for a beginner would be:
synonyms_dict = {'beautiful' : ['pretty', 'lovely', 'handsome', 'dazzling', 'splendid', 'magnificent']}
new_list = []
for key,value in synonyms_dict.items():
for i in range(len(value)):
if len(value[i]) >= 7:
new_list.append(value[i])
synonyms_dict['beautiful'] = new_list
print(synonyms_dict)
Here's a function that modifies the existing dictionary rather than replacing it. This can be useful if you have multiple references to the same dictionary.
synonyms_dict = {
'beautiful' : ['pretty', 'lovely', 'handsome', 'dazzling', 'splendid', 'magnificent']
}
def remove_short_synonyms(d, minlen=7):
for k, v in d.items():
d[k] = [word for word in v if len(word) >= minlen]
remove_short_synonyms(synonyms_dict)
print(synonyms_dict)
output
{'beautiful': ['handsome', 'dazzling', 'splendid', 'magnificent']}
Note that this code does replace the existing lists in the dictionary with new lists. You could keep the old list objects, if you really need to do that, by changing the assignment line to
d[k][:] = [word for word in v if len(word) >= minlen]
although that will be slightly slower, and there's probably no reason to do this.
def remove_short_synonyms(self, **kwargs):
dict = {}
word_list = []
for key, value in synonyms_dict.items():
for v in value:
if len(v) > 7:
word_list.append(v)
dict[key] = word_list
print dict
remove_short_synonyms(synonyms_dict)

Combine list of lists with similar values in python

I have a list of lists, like so:
items = [['118', 'white'], ['118','Jack'], ['118','guilty'], ['200','black'], ['200','mark'], ['200','not guilty']]
Is there a way to use a for loop to grab the second value in each list and collapse it into a new list?
Like so:
['white', 'jack', 'guilty']
['black','mark','not guilty']
Assuming your list always has elements with the same key grouped as in your example, you can use itertools.groupby() to do this efficiently:
>>> import itertools
>>> items = [['118', 'white'], ['118','Jack'], ['118','guilty'], ['200','black'], ['200','mark'], ['200','not guilty']]
>>> [[x[1] for x in g] for k, g in itertools.groupby(items, lambda x: x[0])]
[['white', 'Jack', 'guilty'], ['black', 'mark', 'not guilty']]
You could also use operator.itemgetter(0) as an alternative to lambda x: x[0].
Note that if items does not necessarily have elements grouped by their keys, you can use sorted(items) instead of items in the groupby() call and it will work.
Here is a version that preserves the key as well:
>>> [(k, [x[1] for x in g]) for k, g in itertools.groupby(items, lambda x: x[0])]
[('118', ['white', 'Jack', 'guilty']), ('200', ['black', 'mark', 'not guilty'])]
You could pass this list directly into the dict() built-in function to convert this to a dictionary.
from collections import defaultdict
entries = defaultdict(list)
for (key, value) in items:
entries[key].append(value)
Now entries is a dict of lists of the second values. You can either get them by key ('118') or use values() for a list of lists.
>>> k = set(x[0] for x in items)
>>> [ [x[1] for x in items if x[0] == key] for key in sorted(k) ]
[['white', 'Jack', 'guilty'], ['black', 'mark', 'not guilty']]
output_dict = {}
for each_key in items:
for each_item in each_key:
try:
output_dict[each_key].append(each_item) #fails as I'm trying to use a list as a dict key
except Exception as e:
output_dict[each_key] = [] #see above
output_dict[each_key].append(each_item) #see above
for each_list in output_dict:
print(each_list)
as Peter DeGlopper pointed out below, this code is bad and I should feel bad. I've commented the code to point out my error. There are better solutions, but just to correct my mistake:
items = [['118', 'white'], ['118','Jack'], ['118','guilty'], ['200','black'], ['200','mark'], ['200','not guilty']]
output_dict = {}
for each_list in items:
if each_list[0] not in output_dict: output_dict[each_list[0]] = list()
output_dict[each_list[0]].append(each_list[1])
>>> for each_list in output_dict:
>>> print(each_list)
['white','Jack','guilty']
['black','mark','not guilty']

Sort a list of lists according to another list [duplicate]

This question already has answers here:
How to sort two lists (which reference each other) in the exact same way
(14 answers)
Closed 9 years ago.
How would you sort lista according to order of items in sorter_list:
lista = [["John", "B3"],["Robert", "P3"], ["Thomas", "S2"]]
sorter_list = ["P3", "S2", "B3"]
and result will be:
sorted_lista = [ ["Robert", "P3"], ["Thomas", "S2"], ["John", "B3"]]
Regards
Assuming there will always be an entry in sorter_list that matches the second element of each list in lista:
sorted_lista = sorted(lista, key=lambda lst: sorter_list.index(lst[1]))
Although #F.J has a perfect solution, my question is, why aren't you using a dictionary for storing this kind of data in the first place?
With dictionary:
d = {'B3': 'John', 'P3': 'Robert', 'S2': 'Thomas'}
sorter = ["P3", "S2", "B3"]
print([(d[key], key) for key in sorter])
Output:
[('Robert', 'P3'), ('Thomas', 'S2'), ('John', 'B3')]
Plus: You should also check the collections module's OrderedDict.
UPDATE:
Of course, you can store the values as lists, so one can hold multiple values:
With dictionary:
d = {'B3': [('John', 123)], 'P3': [('Robert', 465), ('Andres', 468)], 'S2': [('Thomas', 19)]}
sorter = ('P3', 'B3', 'S2')
print([(d[key], key) for key in sorter])
Output:
[([('Robert', 465), ('Andres', 468)], 'P3'), ([('John', 123)], 'B3'), ([('Thomas', 19)], 'S2')]
In this case, you can also use a dictionary inside a dictionary:
d = {'B3': {'John': 123}, 'P3': {'Robert': 465, 'Andres': 468}, 'S2': {'Thomas': 19}}
And the lookup will be much easier later on.
You can do this in O(N) by building a dictionary, where your keys are the B3, S2 etc.
lookup_dict = dict( (item[1],item) for item in lista)
sorted_lista = [ lookup_dict[key] for key in sorter_list ]
This takes advantage of the fact that your sorter_list is already sorted.
To sort efficient I think it's better to create a dictionary from sorter_list
sorter_dict = {x:i for i, x in enumerate(sorter_list)}
sorted_lista = sorted(lista, key=lambda lst: sorter_dict[lst[1]])
I'm newbie in python so it may not be the most optimized solution
sorted_lista=[]
for i in sorter_list:
for j in lista:
if i==j[1]:
sorted_lista.append([j[0],j[1]])
print sorted_lista
Output :
[['Robert', 'P3'], ['Thomas', 'S2'], ['John', 'B3']]

Making a dictionary with sets and values

I have the following lists:
keys = ['god', 'hel', 'helo']
values = ['good','god', 'hell', 'hello']
I want to create a dictionary like this:
{'god':set(['god', 'good']), 'hel':'hell', 'helo': 'hello'}
where the key is determined by reducing repeated letters in the value to a single letter.
How would I do this programmatically?
"all repeated letters are reduced to single letters"
Actually according to this rule you don't need the keys list, because it will be created from the values.
Also I would suggest to use a dict of sets for all values, also for the single ones, such as "hell" and "hello". It will make the usage of the dictionary much simpler:
import itertools as it
values = ['good','god', 'hell', 'hello']
d = {}
for value in values:
d.setdefault(''.join(k for k,v in it.groupby(value)), set()).add(value)
# d == {'god': set(['god', 'good']),
# 'hel': set(['hell']),
# 'helo': set(['hello'])}
This should do it for you:
import re
import collections
values = ['good', 'god', 'hell', 'hello']
result = collections.defaultdict(set)
for value in values:
key = re.sub(r'(\w)\1*', r'\1', value)
result[key].add(value)
# result: defaultdict(<type 'set'>, {'hel': set(['hell']), 'god': set(['god', 'good']), 'helo': set(['hello'])})
# if you want to ensure that all your keys exist in the dictionary
keys = ['god', 'hel', 'helo', 'bob']
for key in keys:
result[key]
# result: defaultdict(<type 'set'>, {'hel': set(['hell']), 'god': set(['god', 'good']), 'helo': set(['hello']), 'bob': set([])})
Some code golf (sort of - obviously more obfuscation is possible) upon eumiro's answer, observing that itertools.groupby can be used twice (once to get the letter-sets in order of appearance, something I didn't think of - and again to actually create the key-value pairs for the dictionary).
from itertools import groupby
data = ['good', 'god', 'hell', 'hello']
dict((''.join(k), list(v)) for k, v in groupby(data, lambda x: zip(*groupby(x))[0]))
How it works: each word is first processed with lambda x: zip(*groupby(x))[0]. That is, we take the list of (letter, grouper-object) pairs produced by the groupby generator, transform it into a pair (list-of-letters, list-of-grouper-objects) (the generator contents are implicitly evaluated for passing to zip), and discard the list-of-grouper-objects which we don't want. Then, we group the entire word-list according to the list-of-letters produced by each word, transform the list of letters back into a string, evaluate the grouper-object generators to get the corresponding words, and use those key-value pairs to construct the final dict.
Edit: I guess it's cleaner to do the ''.join step within the lambda:
from itertools import groupby
data = ['good', 'god', 'hell', 'hello']
dict((k, list(v)) for k, v in groupby(data, lambda x: ''.join(zip(*groupby(x))[0])))

Categories