in my list:
animals = [ ['dog', ['bite'] ],
['cat', ['bite', 'scratch'] ],
['bird', ['peck', 'bite'] ], ]
add('bird', 'peck')
add('bird', 'screech')
add('turtle', 'hide')
The add function should check that the animal and action haven't been added before adding them to the list. Is there a way to accomplish this without nesting a loop for each step into the list?
You're using the wrong data type. Use a dict of sets instead:
def add(key, value, userdict):
userdict.setdefault(key, set())
userdict[key].add(value)
Usage:
animaldict = {}
add('bird', 'peck', animaldict)
add('bird', 'screech', animaldict)
add('turtle', 'hide', animaldict)
While it is possible to construct a generic function that finds the animal in the list using a.index or testing with "dog" in animals, you really want a dictionary here, otherwise the add function will scale abysmally as more animals are added:
animals = {'dog':set(['bite']),
'cat':set(['bite', 'scratch'])}
You can then "one-shot" the add function using setdefault:
animals.setdefault('dog', set()).add('bite')
It will create the 'dog' key if it doesn't exist, and since setdefault returns the set that either exists or was just created, you can then add the bite action. Sets ensure that there are no duplicates automatically.
Based on recursive's solution, in Python 2.5 or newer you can use the defaultdict class, something like this:
from collections import defaultdict
a = defaultdict(set)
def add(animal, behavior):
a[animal].add(behavior)
add('bird', 'peck')
add('bird', 'screech')
add('turtle', 'hide')
You really should use a dictionary for this purpose. Or alternatively a class Animal.
You could improve your code like this:
if not any((animal[0] == "bird") for animal in animals):
# append "bird" to animals
animals_dict = dict(animals)
def add(key, action):
animals_dict.setdefault(key, [])
if action not in animals_dict[key]:
animals_dict[key].append(action)
(Updated to use setdefault - nice one #recursive)
While I agree with the others re. your choice of data structure, here is an answer to your question:
def add(name, action):
for animal in animals:
if animal[0] == name:
if action not in animal[1]:
animal[1].append(action)
return
else:
animals.append([name, [action]])
The for loop is an inevitable consequence of your data structure, which is why everyone is advising you to consider dictionaries instead.
Related
Background
I have a module called db.py that is basically consist of wrapper functions that make calls to the db. I have a table called nba and that has columns like player_name age player_id etc.
I have a simple function called db_cache() where i make a call to the db table and request to get all the player ids. The output of the response looks something like this
[Record(player_id='31200952409069'), Record(player_id='31201050710077'), Record(player_id='31201050500545'), Record(player_id='31001811412442'), Record(player_id='31201050607711')]
Then I simply iterate through the list and dump each item inside a dictionary.
I am wondering if there is a more pythonic way to populate the dictionary?
My code
def db_cache():
my_dict: Dict[str, None] = {}
response = db.run_query(sql="SELECT player_id FROM nba")
for item in response:
my_dict[item.player_id] = None
return my_dict
my_dict = db_cache()
This is built-in to the dict type:
>>> help(dict.fromkeys)
Help on built-in function fromkeys:
fromkeys(iterable, value=None, /) method of builtins.type instance
Create a new dictionary with keys from iterable and values set to value.
The value we want is the default of None, so all we need is:
my_dict = dict.from_keys(db.run_query(sql="SELECT player_id FROM nba"))
Note that the value will be reused, and not copied, which can cause problems if you want to use a mutable value. In these cases, you should instead simply use the dict comprehension, as given in #AvihayTsayeg's answer.
my_arr = [1,2,3,4]
my_dict = {"item":item for item in my_arr}
I am trying to find a design pattern (or maybe an algorithm) which will help me write these rules in a cleaner way. Any suggestions?
def get_rules(user, value):
if 500 <= value < 5000 and not user.address:
return [REQUEST_ADDRESS]
if value >= 5000:
if not user.address and not user.phone:
return [REQUEST_ADDRESS, REQUEST_PHONE]
if user.address and not user.phone:
return [REQUEST_PHONE]
if not user.address and user.phone:
return [REQUEST_ADDRESS]
# Potentially ~20 more conditions here based on various attributes of user
return [STATES.REQUEST_NONE]
Note: I am not looking for a rules engine since I don't want to complicate my code by adding "business friendly" DSL in python. Python itself is a simple language to write these rules.
Interesting read: http://martinfowler.com/bliki/RulesEngine.html (but I am still trying to stay away from a "framework" to do this for me).
You're checking lots of different combinations with your "if a and not b else check not a and b else check not a and not b" strategy to figure out what combination of requests you need to send.
Instead, only check what you're missing:
missing = []
if not user.phone:
missing.append(REQUEST_PHONE)
if not user.address:
missing.append(REQUEST_ADDRESS)
return missing or [REQUEST_NONE]
You can use a dict in this case:
resdict = {(False, False): [REQUEST_ADDRESS, REQUEST_PHONE],
(True, False): [REQUEST_PHONE],
(False, True): [REQUEST_ADDRESS]}
return resdict[(user.address, user.phone)]
You can also use a list comprehension:
return [req for req, haveit in zip([REQUEST_ADDRESS, REQUEST_PHONE], [user.address, user.phone]) if not haveit]
Or a simpler list append:
res = []
if not user.address:
res.append(REQUEST_ADDRESS)
if not user.phone:
res.append(REQUEST_PHONE)
If I understood the question right, you have a list of attributes for the user. If one is false a REQUEST value schould be added to the list. Then this could help:
# define all your combinations here:
mapping = {'address': REQUEST_ADDRESS, 'phone': REQUEST_PHONE, …)
return [value for key, value in mapping.items()
if not getattr(user, key, None)]
Looks like your "rules" boil down to this: Request values for fields that are not present as attributes in the object user. I will assume that the mapping of attributes to requests can be arbitrary; you can represent it as a dictionary mapping, e.g. like this:
rulemap = {
"address": REQUEST_ADDRESS,
"phone": REQUEST_PHONE,
# etc.
}
You can then get a list of the requests to issue by checking which of the keys in rulemap are not present as attributes in the object user:
return [ rulemap[fld] for fld in rulemap.keys() if fld not in user.__dict__ ]
I have a list of strings, say something like:
listofstuff = ['string1', 'string2', 'string3', ...]
I have a created a custom class object for what I want to do. All I want now is to create a bunch of said objects that are named the strings in my list. How can I do this?
So I have something like:
for object in listofstuff:
object = classthing(inputs)
But it doesn't work. How do I do this?
EDIT: Maybe I should clarify. I have an input file that can change, and in said input file is a list of names. I want to create a bunch of class objects that are all called the names in the list.
So someone gives me a list like
stuff = ['car1', 'car2', 'car3']
and I now want to create a bunch of new Car objects, each one called car1, car2, etc. So that later I can do things like car1.calculate_price() or whatever.
EDIT 2: Sorry for all the edits, but I also wanted to share something. In what I am trying to do, objects are grouped together in specific ways, but ways that aren't obvious to the user. So it would be like 'car1_car23_car4'. So I wanted, if I asked the user, which car do you want to pick? And they chose car4, it would create an object instead named car1_car23_car4, instead of car4.
Creating names dynamically is not the right approach. It is very easy to loose track of them, to make more or less than you need, or to accidentally overwrite an existing name.
A better approach would be to make a dictionary where the keys are your strings from listofstrings and the values are instances of your class. You can use a dict comprehension and write something like:
dct = {name: classthing(inputs) for name in listofstuff}
Below is a demonstration1 of what this does:
>>> class classthing: # This represents your class
... def __init__(self, name):
... self.name = name
...
>>> listofstuff = ['Joe', 'Bob', 'Mary']
>>>
>>> dct = {name: classthing(name) for name in listofstuff}
>>> dct # dct now holds all the data you need
{'Mary': <__main__.classthing object at 0x0205A6D0>, 'Joe': <__main__.classthing object at 0x0205A690>, 'Bob': <__main__.classthing object at 0x0205A6B0>}
>>>
>>> # Each value in dct is an individual instance of your class
>>> dct['Joe'].name
'Joe'
>>> dct['Bob'].name
'Bob'
>>> dct['Mary'].name
'Mary'
>>>
1For the sake of the demonstration, I replaced inputs with name. You do not have to do this in your real code though.
Assuming your strings are in a list called listofstrings, this creates a corresponsing list constructing objects from the strings (also assuming the __init__ method for the class expects one string argument):
listofobjects = [classthing(s) for s in listofstrings]
If that's what you're looking for, read further about list comprehensions.
While this answers your question, the other answer is probably better way of doing it.
I have a Version class that contains a version_number string, and can obtain a sorted list of those strings like so:
sorted_versions = [v.version_number for v in self.version_set]
sorted_versions.sort(key=LooseVersion)
This works great!
sorted_versions: [u'1.6.3', u'1.7.0']
However, I would like to maintain a sorted list of the actual Version objects, not just their version_number properties, but I'm not sure how to accomplish this.
In my head, I would like to see something like this:
sorted_versions = self.version_set
sorted_versions.sort(key=LooseVersion, property=version_number)
Resulting in:
sorted_versions: [<Version: 1.6.3>, <Version: 1.7.0>]
How about:
sorted(self.version_set, key=lambda v:LooseVersion(v.version_number))
Using sorted() here to avoid changing the original list which is what your 'would like to see' code would do if it worked.
If I understand your question correctly it would be straight forward in implementing it by just modifying the key to the sorted function, to sort by any key as desired.
As an example consider a class
>>> class Version:
def __init__(self,version_number):
self.version_number = version_number
self.blabla = random.randint(1,100)
and list of objects of Version
versions = [Version(i) for i in xrange(1,10)]
and you can sort it as below
sorted_versions = sorted(versions,key=lambda x:x.version_number)
and then if you see the values, it is sorted as per the property you desired
Here's a common situation when compiling data in dictionaries from different sources:
Say you have a dictionary that stores lists of things, such as things I like:
likes = {
'colors': ['blue','red','purple'],
'foods': ['apples', 'oranges']
}
and a second dictionary with some related values in it:
favorites = {
'colors':'yellow',
'desserts':'ice cream'
}
You then want to iterate over the "favorites" object and either append the items in that object to the list with the appropriate key in the "likes" dictionary or add a new key to it with the value being a list containing the value in "favorites".
There are several ways to do this:
for key in favorites:
if key in likes:
likes[key].append(favorites[key])
else:
likes[key] = list(favorites[key])
or
for key in favorites:
try:
likes[key].append(favorites[key])
except KeyError:
likes[key] = list(favorites[key])
And many more as well...
I generally use the first syntax because it feels more pythonic, but if there are other, better ways, I'd love to know what they are. Thanks!
Use collections.defaultdict, where the default value is a new list instance.
>>> import collections
>>> mydict = collections.defaultdict(list)
In this way calling .append(...) will always succeed, because in case of a non-existing key append will be called on a fresh empty list.
You can instantiate the defaultdict with a previously generated list, in case you get the dict likes from another source, like so:
>>> mydict = collections.defaultdict(list, likes)
Note that using list as the default_factory attribute of a defaultdict is also discussed as an example in the documentation.
Use collections.defaultdict:
import collections
likes = collections.defaultdict(list)
for key, value in favorites.items():
likes[key].append(value)
defaultdict takes a single argument, a factory for creating values for unknown keys on demand. list is a such a function, it creates empty lists.
And iterating over .items() will save you from using the key to get the value.
Except defaultdict, the regular dict offers one possibility (that might look a bit strange): dict.setdefault(k[, d]):
for key, val in favorites.iteritems():
likes.setdefault(key, []).append(val)
Thank you for the +20 in rep -- I went from 1989 to 2009 in 30 seconds. Let's remember it is 20 years since the Wall fell in Europe..
>>> from collections import defaultdict
>>> d = defaultdict(list, likes)
>>> d
defaultdict(<class 'list'>, {'colors': ['blue', 'red', 'purple'], 'foods': ['apples', 'oranges']})
>>> for i, j in favorites.items():
d[i].append(j)
>>> d
defaultdict(<class 'list'>, {'desserts': ['ice cream'], 'colors': ['blue', 'red', 'purple', 'yellow'], 'foods': ['apples', 'oranges']})
All of the answers are defaultdict, but I'm not sure that's the best way to go about it. Giving out defaultdict to code that expects a dict can be bad. (See: How do I make a defaultdict safe for unexpecting clients? ) I'm personally torn on the matter. (I actually found this question looking for an answer to "which is better, dict.get() or defaultdict") Someone in the other thread said that you don't want a defaultdict if you don't want this behavior all the time, and that might be true. Maybe using defaultdict for the convenience is the wrong way to go about it. I think there are two needs being conflated here:
"I want a dict whose default values are empty lists." to which defaultdict(list) is the correct solution.
and
"I want to append to the list at this key if it exists and create a list if it does not exist." to which my_dict.get('foo', []) with append() is the answer.
What do you guys think?