Remove element in a list with condition - python

I have a class named Info where Info has a string type instance variable which can be accessed by Info.getName()
Also I have a list of instance Info such as class_list = [Info('Aleck'), Info('John')].
Given a name_list = ['Aleck', 'Bob'], I would like to remove the element in class_list with the same name in name_list, while i also need to know if a name (such as Bob) is not in class_list (for example print out that bob is not in list)
for above example the result should be class_list = [Info('John')] and print out that bob is not on the list.
I know ugly way of doing so such as the following codes (i am not actually running it, just an example), is there elegant or Pythonic way to do this?
def removeElement(name_list, class_list):
list_to_be_removed = []
for name in name_list:
is_name_in_list = false
for obj in class_list
if name == obj.getName():
list_to_be_removed.add(obj)
is_name_in_list = true
break
if is_name_in_list == false:
print name + ' is not in the list'
is_name_in_list = false
for obj in list_to_be_removed:
class_list.remove(obj)

You don't have classes, you have class instances. Info('Alice') is a constructor call, that creates an instance of your Info class.
How about this:
filtered = [info for info in info_list if info.get_name() not in name_list]
Alternatively:
filtered = filter(lambda o: o not in name_list, info_list)
If you have a lot of names convert your name_list into a set and use that.
name_set = set(name_list)

List comprehensions are your friend.
def remove_names(names_to_remove, info_list):
return [info
for info in info_list
if info.getName() not in names_to_remove]
I changed the names of most variables to be closer to what I think you mean.

Related

How to modify a global variable in python and how to add a list to an array that is returned by a function

I'm currently having issues with a program I'm making. It has four functions:
Decodes the string (obligatory to use it as string) into an array:
list = 'Ana;Mora;Tencio°3-3456-0987°F ^Juan;Arias;Quirós°1-0654-1345°M ^Enrique;Miranda;Ortega°5-6547-8745°M ^' \'María;Montero;Fonseca°1-6654-7458°F ^Angela;Montero;Fonseca°2-6654-7458°F'
def decodeList():
person = list.split('^')
sepNCG = [sep.split('°') for sep in person]
names= [sepX[0] for sepX in sepNCG]
name = [i.split(';',1)[0] for i in names]
firstLn= [i.split(';',2)[1] for i in names]
secondLn = [i.split(';',3)[2] for i in names]
code= [code[1] for code in sepNCG]
gender = [gen[2] for gen in sepNCG]
n = 0
array= []
while n != len(person):
person = [[name[n],firstLn[n],secondLn[n]],code[n],gender[n]]
n += 1
array.append(person)
return array
Now the second one should add a new person to the string (the parameters come from an input function):
def add(name,firstLn,secondLn,code,gender):
global list
if gender == True:
gender= 'M'
elif gender == False:
gender = 'F'
person = '^'+name+';'+firstLn+';'+secondLn+'°'+code+'°'+gender+'^'
list = list + person
return list
Now, this returns the string with the new data, but if I were to run the decode function again (using a menu so the program isn't stopped) the new data isn't there.
Now for the third one:
def removePerson(code):
global lista
for i in lista:
if code in i:
lista.remove(i)
return lista
elif cedula not in lista:
return 'Person not in list'
With this one happens the same, so my question is how do I do change it so that add and removePerson change the string so that when I run the decodeList function it does it with the changes previously made?

Trying to store a dictionary within a dictionary overwrites existing values

I'm trying to make a dictionary which calls the name of a restaurant, and the type of cuisine it is known for.
I wanted to create a dictionary so I could call each restaurant type up later. The problem is every method I've tried so far overwrites my value pairs which pertain to each key.
I understand that I somehow need to alter each key so as not to overwrite my values, but so far every attempt I've tried has not been successful. To circumvent that, I tried to place a dictionary within a dictionary. The input/output of the code is also seen below.
Some of the things I attempted are below:
restaurant_dict[restaurant] = value
main:
from nine_two import Restaurant
def increment(min_num, max_num, name_of_variable):
#enter the name of the variable and how many times to increment it
list_1 = []
for x in range(min_num,max_num):
list_1.append(f"{name_of_variable}_{x}")
for variable in list_1:
#return a list of the incremented variables
return list_1
#Created dictionaries & variables
restaurant_dict = {}
restaurant_dict_2 = {}
list_1 = increment(1,4,"Restaurant")
for variable in list_1:
print(f"\n{variable}:")
user_restaurant = input("Enter name of restaurant: ")
user_cuisine = input("Enter cuisine of restaurant: ")
#FIXME attempt to store variables in a callable dictionary
restaurant_dict_2[variable] = restaurant_dict
restaurant_dict["Restaurant"] = user_restaurant
restaurant_dict["Cuisine type"] = user_cuisine
print(restaurant_dict_2)
#ignore this
variable = Restaurant(user_restaurant,user_cuisine)
variable.describe_restaurant()
variable.open_restaurant()
Imported code is:
class Restaurant:
"""Creates a restaurant"""
def __init__(self, restaurant_name, cuisine_type):
"""Intialize name and cuisine attributes."""
self.name = restaurant_name
self.cuisine = cuisine_type
def describe_restaurant(self):
"""describes the restaurant"""
print(f"The {self.name} is a restaurant that specializes in {self.cuisine}.")
def open_restaurant(self):
"""Opens the restaurant"""
print(f"The {self.name} is now open!")
Output of code above
This answer comes in two parts:
How to fix your dictionary problem.
You don't need to use a dictionary at all.
Part 1: How to fix the problem with your dictionaries:
Your problem is that you assign the same restaurant_dict to multiple keys. When you do dict2['key1'] = dict1, you didn't create a copy of dict1 to assign to dict2['key1']. So when later you do dict2['key2'] = dict1, the same dict gets assigned to both key1 and key2. Then, when you change dict1['name'], since the same dict1 is referenced by both key1 and key2, it changes the value in both places.
To fix this, move the line restaurant_dict = {} inside your for variable in list_1: loop, so that you create a new dictionary for each restaurant.
#FIXME attempt to store variables in a callable dictionary
restaurant_dict = {}
restaurant_dict_2[variable] = restaurant_dict
restaurant_dict["Restaurant"] = user_restaurant
restaurant_dict["Cuisine type"] = user_cuisine
Or, better yet, you can simply assign the Restaurant and Cuisine type keys while you're creating the dict
#FIXME attempt to store variables in a callable dictionary
restaurant_dict = {"Restaurant": user_restaurant, "Cuisine type": user_cuisine}
restaurant_dict_2[variable] = restaurant_dict
Part 2: You don't need to use a dictionary at all
Usually when you use a dictionary, you want to map a key to a value. For example, if you were creating a directory of restaurants for every cuisine, you could have a dictionary where the keys were the cuisine type, and the values were a list of restaurants serving that cuisine.
However, in this case, it seems like you are simply making a list of restaurants (because your keys are sequential), so a simple list would suffice. Also, since you have a class to represent a restaurant, you just need to create an object of this class and append it to your list. No need to create dictionaries and Restaurant objects.
restaurants = []
for _ in range(num_restaurants):
user_restaurant = input("Enter name of restaurant: ")
user_cuisine = input("Enter cuisine of restaurant: ")
restaurant = Restaurant(user_restaurant, user_cuisine)
restaurants.append(restaurant)
restaurant.describe_restaurant()
restaurant.open_restaurant()
If you wanted to do the thing I mentioned in the previous paragraph and create a map of cuisines with restaurants, you could then process this list into a dict:
cuisine_restaurants = {}
for rest in restaurants:
cuisine = rest.cuisine
if cuisine not in cuisine_restaurants:
cuisine_restaurants[cuisine] = []
cuisine_restaurants[cuisine].append(rest)
Then, you could ask the user for a cuisine, and show all restaurants that serve that cuisine without having to re-iterate over the entire restaurants list:
c = input("What would you like to eat today?")
suggested_rest = cuisine_restaurants.get(c, []) # .get() returns the second argument if the key given by c doesn't exist
print("You could eat at: ")
for r in suggested_rest:
r.describe_restaurant()

Refactoring For-Loops in Python

I am currently working on an iTunes data program that's cycling constantly through a user's library to get statistics about one's library.
returns
I have a few code snippets like these:
def numArtist(self):
num = 0
for song in self.allSongs:
tempList = []
if song.artist not in tempList:
tempList.append(song.artist)
num += 1
return num
def getAlbumNames(self):
albums = []
for song in self.allSongs:
if song.album not in albums:
albums.append(song.album)
return albums
Where the main for loop body is repeated:
for song in self.allSongs: # same for-loop condition
# different for-loop body
Is there a way to refactor methods like these, where I have the same for loop conditions but with different body definitions?
I have a quite a few methods with the same for-loop, so I'd like to find a way to decrease the complexity and redundancy of my code.
Just for reference, all Song objects have attributes - artist, album (name),genre, etc - that I'm using to get my data.
Use set comprehensions and len to simplify each of them:
def numArtist(self):
return len({song.artist for song in self.allSongs})
def getAlbumNames(self):
return {song.album for song in self.allSongs}
To make it more generic, you could write a method that takes a lambda and use that to filter the property out of each song:
def uniqueProps(self, fxn):
return {fxn(song) for song in self.allSongs}
def getAlbumNames(self):
return self.uniqueProps(lambda song: song.album)
You can use set comprehensions for both snippets, if that counts as a valid "For-Loop refactoring":
artist_count = len({song.artist for song in self.allSongs})
album_names = set({song.album for song in self.allSongs})
Generic version using getattr
get_values = lambda objs, attr: {getattr(obj, attr) for obj in objs
attributes = 'artist', 'album'
values = [get_values(self.allSongs, name) for name in attributes]
artists, albums = values
artist_count = len(artists)
Generic version using lambda
get_artist = lambda song: song.artist
get_album = lambda song: song.album
getters = get_artist, get_album
values = [
{func(song) for song in self.allSongs}
for getter in getters
]
artists, albums = values
artist_count = len(artists)
Generic version using property
# If `song` is an instance of the `Song` class and both `artist` and
# `album` are properties defined on the class, it's also possible to
# directly use the property getter (`property.fget`) to avoid defining
# the lambdas manually:
get_artist = Song.artist.fget
get_album = Song.album.fget
... # <same as above>
If the contents of your allSongs list are immutable - which I suspect they are - you can convert your lists to sets and back to lists again - or use set comprehension - to get rid of duplicates. Then your functions can be greatly simplified like so:
def numArtist(self):
return len({song.artist for sing in self.allSongs})
def getAlbumNames(self):
return list({song.album for song in self.allSongs})
If you're not sure if the song objects are mutable or not, try this out anyway. If they're mutable objects you'll get an exception like:
TypeError: unhashable type: ...
You could try to create the generators, that produces the value of song attributes. Let me give you an example:
def gen_attr(songs, attr_name):
for song in songs:
yield getattr(song, attr_name)
class Song(object):
def __init__(self, name, artist):
self.name = name
self.artist = artist
class Album(object):
def __init__(self, songs_list):
self.songs_list = songs_list
def allSongs(self):
return self.songs_list
s = Song('Ahoy', 'Pirate')
s1 = Song('Bye', 'My Son')
s2 = Song('Ahoy', 'Captain')
a = Album([s, s1])
Now if you want to get all of the song names, u can use:
song_names = list(gen_attr(a.allSongs(), 'name'))
print(song_names) # ['Ahoy', 'Bye', 'Ahoy']
For non-repeated song names you would use:
song_names = list(set(gen_attr(a.allSongs(), 'name')))
print(song_names) # ['Ahoy', 'Bye']
To count the non-repeated artists names, you would use:
artists = len(set(gen_attr(a.allSongs(), 'artist')))
And to create the list of artists, just go for:
artists = list(gen_attr(a.allSongs(), 'artist'))
print(artists) # ['Pirate', 'My Son', 'Captain']

Calling Object inside of List

class MySong:
_songTitle = "Song Title"
_artistName = "Artist Name"
_likeIndicator = -1
def setTitleAndArtist(self, songTitle, artistName):
self._songTitle = songTitle
self._artistName = artistName
def setLike(self, likeIndicator):
self._likeIndicator = likeIndicator
def undoSetLike(self, songTitle):
Null
def getTitle(self):
return self._songTitle
def getArtist(self):
return self._artistName
def getLikeIndicator(self):
return self._likeIndicator
class MyPlaylist:
_mySongs = []
def add(self, song):
self._mySongs.append(song)
def showTitles(self):
index = 0
titlesList = []
while index != len(self._mySongs):
titlesList.append(self._mySongs[index].getTitle())
index = index + 1
return titlesList
def remove(self):
remindex = 0
while remindex != len(self._mySongs):
if (self._mySongs[index].getTitle()) == remChoice :
return("Song FOUND debug!")
self._mySongs.remove(index)
else:
remindex = remindex + 1
return("Song NOT FOUND debug!")
def getMySong(self):
Null
There is a list of song objects inside of _mySongs = []. I'm trying to remove one, based on the title variable of that object.
In a separate (unshown) part of the program, the user is asked to enter the title of the song they want removed as a string. This is saved as remChoice.
I'm not entirely sure how to remove the song based on the title.
I've tried for a while to get it going, obviously we find the index of the song in the list by matching it to the title (by calling the getTitle method), then removing that index when it's found.
This isn't working. Where am I going wrong?
If you want to delete an item from a list knowing it's index use:
del xs[i]
Where i is the index. (e.g: Your song's index based on your search).
list.remove() is used for removing a matching element form the list not the "ith" item.
You might also find that a list is not a suitable data structure here? Perhaps you could try storing key/value pairs in a dict. e.g:
my_songs = {}
my_aongs["My Song Title"] = MySong(title, description, length)
You can later delete songs via their keys:
del my_songs["My Song Title"]
where titles are your keys. This saves you from doing O(n) searching.
Update:
Your .remove() method should look more like the following:
def remove(self, title):
for i, song in enumerate(self._mySongs):
if song.getTitle() == title:
del self._mySongs[i]
return
print("Song not found!")
Here we're using list's iteration protocol by using a for x in xs: rather than using a while loop and doing manual bookkeeping. The builtin function enumerate() is also used to give us an index into the list we're iterating over (i.e: it's position in the sequence).
try
self._mySongs.remove(title)
That should work.
(Or from another object: replace self by whatever your object name is)

Applying the #staticmethod, python3

class UserInput():
users=[]
def __init__(self, name,lista,listb,listc,listd):
self.name=""
self.lista=lista
self.listb=listb
self.listc=listc
self.listd=listd
#staticmethod
def create_new_user(x):
x=userinput("x","","","","")
users.append(x)
Im intending on making a function where new users are generated, only returning a name to the user and no lists yet, hence x in the name slot.
My Question: is this the correct usage of #staticmethod or did I miss the entire point of it?
To my understanding, it allows the user to use,in this case, userinput.create_new_user('tim') without having the class already pre-defined, tim=userinput("foo","","","","");it creates it on the spot.
What I was trying to turn the function create_new_users into:
#staticmethod
def create_new_user():
print("how many users do you want to create")
x=int(input())
y=0
while y < x:
print("assign the users names")
name = input("")
if name == "" or "None,none":
raise SyntaxError("name cannot be None or empty")
break
name=userinput("","","","","")
userinput.users.append(name)
y+=1
in a static method you could not use the class variable, your code should get
NameError: global name 'users' is not defined
edit:
use userinput.users.append
Using a #classmethod will be the easiest alternative for that.
class UserInput: # capitals! Look at PEP 8.
users = [] # rearranged to the top for better readability
def __init__(self, name, lista, listb, listc, listd):
self.name = ""
self.lista = lista
self.listb = listb
self.listc = listc
self.listd = listd
#classmethod
def create_new_user(cls): # no need for x if you overwrite it immediately
x = cls("x", "", "", "", "")
cls.users.append(x) # easier access to this static attribute
return x # for the caller having access to it as well.
It works as well if we subclass UserInput as it uses the new class then.
But note that x = cls("x", "", "", "", "") won't be very useful, though; better do
#classmethod
def create_new_user(cls, *a, **k): # no need for x if you overwrite it immediately
x = cls(*a, **k) # pass the arguments given by the caller to __init__.
cls.users.append(x) # easier access to this static attribute
return x # for the caller having access to it as well.
I can use that now this way:
a = UserInput("foo", "whatever", "is", "needed", "here")
or, if I choose to,
a = UserInput.create_new_user("foo", "whatever", "is", "needed", "here")
which additionally appends the new user to the list.
If you want to be able to shorten the arguments list, you can do so as well:
def __init__(self, name, lista=None, listb=None, listc=None, listd=None):
self.name = name
self.lista = lista if lista is not None else []
self.listb = listb if listb is not None else []
self.listc = listc if listc is not None else []
self.listd = listd if listd is not None else []
if they are really lists. If they are strings, another name would be appropriate and, as strings are immutable, you can simply do
def __init__(self, name, lista='', listb='', listc='', listd=''):
self.name = name
self.lista = lista
self.listb = listb
self.listc = listc
self.listd = listd
and call the stuff with
a = UserInput.create_new_user("foo", listc=...) # all others are left empty
b = UserInput("bar") # all are left empty
c = UserInput.create_new_user("ham", lista=..., listd=...) # all others are left empty
Now that you come up with a different task, I'll try to cope with that as well:
#classmethod
def create_new_users(cls): # several users!
print("how many users do you want to create")
num = int(input())
for _ in range(num): # simpler iteration
print("enter the user's name")
name = input("") # in 3.x, this is always a string, so it cannot be None...
# if name == "" or "None,none": # That won't work as you think.
if name == '' or name.lower() == 'none': # but why disallow the string 'None'?
# raise SyntaxError("name cannot be None or empty")
raise RuntimeError("name cannot be None or empty") # or ValueError or alike
# break not needed. raise jumps out without it as well.
user = cls(name, "", "", "", "") # name is an input, not an output.
cls.users.append(name)
But I wonder if the class is really the right place to store new users, and only those created with this function. Maybe it would be better to feed the users list directly in __init__ and let this function be at a higher level.
The advantage of using a #classmethod here is that you always work on the corret basis.
Imagine you have a UserInput with a __init__() method as above. Then you can subclass it and do
UserInput.create_new_users()Using a #classmethod will be the easiest alternative for that.
class UserInputStoring(UserInput):
users = [] # this is only here, not at the parent.
def __init__(self, *a, **k):
super(UserInputStoring, self).__init__(*a, **k) # pass everything up as it was used
self.users.append(self)
Now you can have your create_new_users() in the base class and be a #classmethod and it will pick the right __init__ to call depending on how you call it.

Categories