Class wordplay with four methods - python

I have the following problem and two very important questions.
Write a class called Wordplay. It should have a field that holds a list of words. The user
of the class should pass the list of words they want to use to the class. There should be the
following methods:
words_with_length(length) — returns a list of all the words of length length
starts_with(s) — returns a list of all the words that start with s
ends_with(s) — returns a list of all the words that end with s
palindromes() — returns a list of all the palindromes in the list
First problem. After compiling my program the methods starts with and ends with return the same word.
Next problem. In this case i have created a list of three names. But what if i wanted to ask for a list size and iterate over it while asking to input a word. How can i implement that idea?
class Wordplay:
def __init__(self):
self.words_list=[]
def words_with_lenght(self,lenght):
for i in range(0,len(self.words_list)-1):
if len(self.words_list[i])==lenght:
return self.words_list[i]
def starts_with_s(self,s):
for i in range(0,len(self.words_list)-1):
if s.startswith('s')==True:
return self.words_list[i]
def ends_with_s(self,s):
for i in range(0,len(self.words_list)-1):
if s.endswith('s')==True:
return self.words_list[i]
def palindromes(self):
for i in range(0,len(self.words_list)-1):
normal_word=self.words_list[i]
reversed_word=normal_word[::-1]
if reversed_word==normal_word:
return reversed_word
verification=Wordplay()
verification.words_list=['sandro','abba','luis']
lenght=int(input('Digit size you want to compare\n'))
s='s'
print(verification.words_with_lenght(lenght))
print(verification.starts_with_s(s))
print(verification.ends_with_s(s))
print(verification.palindromes())
If i input for example size 4 i expect the result to be:
abba,luis ; sandro ; luis ; abba and not-
abba; sandro ; sandro ; abba

In the line if s.startswith('s')==True:, you've passed the string "s" into the function resulting in
if 's'.startswith('s')==True:
# ^^^
return self.words_list[i]
This conditional is always true. You probably don't need a parameter here at all since the assignment asks you to hard code "s". You can use:
if self.words_list[i].startswith('s'):
return self.words_list[i]
Notice the above example uses a return as soon as a match is found. This is a problem. The loops in this program break early, returning from the function as soon as a single match is located. You may have intended to append each successful match to a list and return the resulting list or use the yield keyword to return a generator (but the caller would need to use list() if they want a persistent list from the generator). Using a list to build a result would look like:
result = []
for i in range(len(self.words_list)):
if self.words_list[i].startswith('s'):
result.append(self.words_list[i])
return result
Another issue: the loops in this program don't iterate all the way through their respective lists. The range() function is inclusive of the start and exclusive of the end, so you likely intended range(len(self.words_list)) instead of range(0, len(self.words_list) - 1).
Beyond that, there are a number of design and style points I'd like to suggest:
Use horizontal space between operators and use vertical whitespace around blocks.
foo=bar.corge(a,b,c)
if foo==baz:
return quux
is clearer as
foo = bar.corge(a, b, c)
if foo == baz:
return quux
Use 4 spaces to indent instead of 2, which makes it easier to quickly determine which code is in which block.
Prefer for element in my_list instead of for i in range(len(my_list)). If you need the index, in most cases you can use for i, elem in enumerate(my_list). Better yet, use list comprehensions to perform filtering operations, which is most of this logic.
There's no need to use if condition == True. if condition is sufficient. You can simplify confusing and inaccurate logic like:
def palindromes(self):
for i in range(0,len(self.words_list)-1):
normal_word=self.words_list[i]
reversed_word=normal_word[::-1]
if reversed_word==normal_word:
return reversed_word
to, for example:
def palindromes(self):
return [word for word in self.words_list if word[::-1] == word]
that is, avoid intermediate variables and indexes whenever possible.
I realize you're probably tied down to the design, but this strikes me as a strange way to write a utility class. It'd be more flexible as static methods that operate on iterables. Typical usage might be like:
from Wordplay import is_palindrome
is_palindrome(some_iterable)
instead of:
wordplay = Wordplay(some_iterable)
wordplay.palindromes()
My rationale is that this class is basically stateless, so it seems odd to impose state when none is needed. This is a bit subjective, but worth noting (if you've ever used the math or random modules, it's the same idea).
The lack of parameter in the constructor is even weirder; the client of the class has to magically "know" somehow that words_list is the internal variable name they need to make an assignment to in order to populate class state. This variable name should be an implementation detail that the client has no idea about. Failing providing a parameter in the initialization function, there should be a setter for this field (or just skip internal state entirely).
ends_with_s(self, s) is a silly function; it seems the designer is confused between wanting to write ends_with(self, letter) and ends_with_s(self) (the former is far preferable). What if you want a new letter? Do you need to write dozens of functions for each possible ending character ends_with_a, ends_with_b, ends_with_c, etc? I realize it's just a contrived assignment, but the class still exhibits poor design.
Spelling error: words_with_lenght -> words_with_length.
Here's a general tip on how to build the skill at locating these problems: work in very small chunks and run your program often. It appears that these four functions were written all in one go without testing each function along the way to make sure it worked first. This is apparent because the same mistakes were repeated in all four functions.

s.endswith('s') compares your input string s ("s") with "s". "s" ends in "s", so it always returns your first entry. Change it to if self.words_list[i].startswith('s'): (same for endswith).
I would recommend changing your for loops to iterate over the words themselves though:
def ends_with_s(self, s):
for word in self.words_list:
if word.endswith('s'):
return word
Entering a list of values as you described:
amount = int(input("How many words? "))
words = [input("Word {}".format(i + 1)) for i in range(amount)]

Related

Zen of Python 'Explicit is better than implicit'

I'm trying to understand what 'implicit' and 'explicit' really means in the context of Python.
a = []
# my understanding is that this is implicit
if not a:
print("list is empty")
# my understanding is that this is explicit
if len(a) == 0:
print("list is empty")
I'm trying to follow the Zen of Python rules, but I'm curious to know if this applies in this situation or if I am over-thinking it?
The two statements have very different semantics. Remember that Python is dynamically typed.
For the case where a = [], both not a and len(a) == 0 are equivalent. A valid alternative might be to check not len(a). In some cases, you may even want to check for both emptiness and listness by doing a == [].
But a can be anything. For example, a = None. The check not a is fine, and will return True. But len(a) == 0 will not be fine at all. Instead you will get TypeError: object of type 'NoneType' has no len(). This is a totally valid option, but the if statements do very different things and you have to pick which one you want.
(Almost) everything has a __bool__ method in Python, but not everything has __len__. You have to decide which one to use based on the situation. Things to consider are:
Have you already verified whether a is a sequence?
Do you need to?
Do you mind if your if statement crashed on non-sequences?
Do you want to handle other falsy objects as if they were empty lists?
Remember that making the code look pretty takes second place to getting the job done correctly.
Though this question is old, I'd like to offer a perspective.
In a dynamic language, my preference would be to always describe the expected type and objective of a variable in order to offer more purpose understanding. Then use the knowledge of the language to be succinct and increase readability where possible (in python, an empty list's boolean result is false). Thus the code:
lst_colours = []
if not lst_colours:
print("list is empty")
Even better to convey meaning is using a variable for very specific checks.
lst_colours = []
b_is_list_empty = not lst_colours
if b_is_list_empty:
print("list is empty")
Checking a list is empty would be a common thing to do several times in a code base. So even better such things in a separate file helper function library. Thus isolating common checks, and reducing code duplication.
lst_colours = []
if b_is_list_empty(lst_colours):
print("list is empty")
def b_is_list_empty (lst):
......
Most importantly, add meaning as much as possible, have an agreed company standard to chose how to tackle the simple things, like variable naming and implicit/explicit code choices.
Try to think of:
if not a:
...
as shorthand for:
if len(a) == 0:
...
I don't think this is a good example of a gotcha with Python's Zen rule of "explicit" over "implicit". This is done rather mostly because of readability. It's not that the second one is bad and the other is good. It's just that the first one is more skillful. If one understands boolean nature of lists in Python, I think you find the first is more readable and readability counts in Python.

Detect empty list vs list of empty strings

I am new to Python, and I have searched for solution for detecting empty list. Say we have an empty string, like:
a = ''
if not a:
print('a is empty')
This works fine. However, my problem arises when:
a = ['','','']
if not a:
print('a is empty')
This one does not work. What is the Pythonic way of detecting a which I guess is a list containing empty strings in the above case?
Thanks in advance for your comments and suggestions.
A list is only empty if it has no members. It has members, but they're empty, it still has members, so it's still truthy.
If you want to test whether all of the members of a list are empty, you can write it like this:
if all(not element for element in a):
print('a is made of empty things')
The all function should be pretty obvious. The argument to it might not be. It's a generator expression—if you've never seen one, first read about list comprehensions, then iterators and the following two sections.
Or you can turn it around and test whether not any of the members are not empty:
if not any(a):
print('a is made of empty things')
The double negative seems a bit harder to understand. But on the other hand, it means you don't need to understand a generator expression (because (element for element in a) is just the same thing as a, so we can keep it simple).
(And, as PM 2Ring points out, this one probably a little faster, although probably not enough to matter for most uses.)

making python code block with loop faster

Is there a way I can implement the code block below using map or list comprehension or any other faster way, keeping it functionally the same?
def name_check(names, n_val):
lower_names = names.lower()
for item in set(n_val):
if item in lower_names:
return True
return False
Any help here is appreciated
A simple implementation would be
return any(character in names_lower for character in n_val)
A naive guess at the complexity would be O(K*2*N) where K is the number of characters in names and N is the number of characters in n_val. We need one "loop" for the call to lower*, one for the inner comprehension, and one for any. Since any is a built-in function and we're using a generator expression, I would expect it to be faster than your manual loop, but as always, profile to be sure.
To be clear, any short-circuits, so that behaviour is preserved
Notes on Your Implementation
On using a set: Your intuition to use a set to reduce the number of checks is a good one (you could add it to my form above, also), but it's a trade-off. In the case that the first element short circuits, the extra call to set is an additional N steps to produce the set expression. In the case where you wind up checking each item, it will save you some time. It depends on your expected inputs. If n_val was originally an iterable, you've lost that benefit and allocated all the memory up front. If you control the input to the function, why not just recommend it's called using lists that don't have duplicates (i.e., call set() on its input), and leave the function general?
* #Neopolitan pointed out that names_lower = names.lower() should be called out of the loop, as your original implementation called it, else it may (will?) be called repeatedly in the generator expression

Python 'pointer arithmetic' - Quicksort

In idiomatic C fashion, one can implement quicksort in a simple way with two arguments:
void quicksort(int inputArray[], int numelems);
We can safely use two arguments for later subdivisions (i.e. the partitions, as they're commonly called) via pointer arithmetic:
//later on in our quicksort routine...
quicksort(inputArray+last+1, numelems-last-1);
In fact, I even asked about this before on SO because I was untrained in pointer arithmetic at the time: see Passing an array to a function with an odd format - “v+last+1”
Basically, Is it possible to replicate the same behavior in python and if so, how? I have noticed that lists can be subdivided with the colon inside of square brackets (the slicing operator), but the slice operator does not pass the list from that point on; that is to say that the 1st element (0th index) is still the same in both cases.
As you're aware, Python's slice syntax makes a copy, so in order to manipulate a subsection of a list (not "array", in Python) in place, you need to pass around both the list and the start-index and size (or end-index) of the portion under discussion, much as you could in C. The signature of the recursive function would be something like:
def quicksort( inputList, numElems, startIndex = 0 ):
And the recursive call would be something like:
quicksort( inputList, numElems-last-1, last+1 )
Throughout the function you'd add startIndex to whatever list accesses you would make.
I suppose if you want to do something like that you could do the following:
# list we want to mutate
sort_list = [1,2,3,4,5,6,7,8,9,0]
#wrapper just so everything looks pretty, process could go here if we wanted
def wrapper(a, numlems):
cut = len(a) - numlems
# overwrites a part of the list with another one
a[cut:] = process(a[cut:])
# processing of the slice
def process(a):
# just to show it works
a[1] = 15
return a
wrapper(sort_list, 2)
print(sort_list)
wrapper(sort_list, 4)
print(sort_list)
wrapper(sort_list, 6)
print(sort_list)
This is probably considered pretty evil in python and I wouldn't really recommend it, but it does emulate the functionality you wanted.
For python you only really need:
def quicksort(inputList, startIndex):
Then creating and concatenating slices would work fine without the need for pointer like functionality.

League table in Python - it doesn't insert teams into the list

I need to make a league table for a project. There has to be 3 files,2 files consist of 1 class and the last file is for running a program. I have done all of the parts but when I call a method to add a team, the program adds the name but it does not insert it into the list of teams(which should do). When I try to display the items in the list, the program displays an error message instead of showing the actual team.
How can I fix it?Any help would be appreciated. :)
A few things here:
When I try to display the items in the list, the program displays: team.Team object at 0x000000000332A978 insted of showing the actual team.
The default display for a user class is something like <team.Team object at 0x000000000332A978>. If you want it to display something different, you have to tell Python what you want to display. There are two separate functions for this: __repr__ and __str__. The idea is that the first is a representation for the programmer, the second for the user. If you don't need two different representations, just define __repr__ and it'll use that whenever it needs __str__.
So, a really simple way to fix this is to add this to the Team class:
def __repr__(self):
return 'Team("{}")'.format(self._name)
Now, if you call league.addTeam('Dodgers'), then print(l._table), you'll get [Team("Dodgers")] instead of [<team.Team object at 0x000000000332A978>].
Meanwhile, these two methods are probably not what you want:
def removeTeam(self,team):
self._table.remove(team)
def returnPosition(self,team):
return self._table.index(team)
These will remove or find a team given the Team object—not the name, or even a new Team created from the name, but a reference to the exact same object stored in the _table. This is not all that useful, and you seem to want to call them with just names.
There are two ways to fix this: You could change Team so that it compares by name instead of by object identity, by adding this method to the class:
def __eq__(self, other):
return self._name == other._name
What this means is that if you say Team('Giants') == Team('Giants'), it will now be true instead of False. Even if the first team is in a different league, and has a different W-L record, and so on (e.g., like the baseball "Giants" from San Francisco vs. the football "Giants" from New York), as far as Python is concerned, they're now the same team. Of course if that's not what you want, you can write any other __eq__ function that seems more appropriate.
Anyway, if you do this, the index and remove functions will now be able to find any Team with the same name, instead of just the exact same team, so:
def removeTeam(self,team_name):
self._table.remove(Team(team_name))
def returnPosition(self,team_name):
return self._table.index(Team(team_name))
If you go this way, you might want to consider defining all of the comparison methods, so you can, e.g., sort a list of teams, and they sort by name.
Or you could change these methods so they don't work based on equality, e.g., by redefining them like this:
def removeTeam(self,team_name):
self._table = [team for team in self._table if team._name != team_name]
def returnPosition(self,team_name):
return [team._name for team in self._table].index(team_name)
To understand how these work, if you're not used to reading list comprehensions, turn each one back into the equivalent loop:
self._table = [team for team in self._table if team._name != team_name]
temp = []
for team in self._table:
if team._name != team_name:
temp.append(team)
self._table = temp
If you step through this, temp ends up with a list of every team in the table, except the one you wanted to remove, and then you replace the old self._table with the new filtered one. (Another way to write the same idea is with filter, if you know that function.)
It's usually better to create a new filtered list than to modify a list in-place. Sometimes there are performance reasons not do this, and sometimes it ends up being very complex and hard to understand, but it's usually both faster and simpler to reason about. Also, modifying lists in place leads to problems like this:
for i, value in enumerate(mylist):
if value == value_to_remove:
del mylist[i]
Play with this for a while, and you'll see that it doesn't actually work. Understanding why is a bit complicated, and you probably don't want to learn that until later. The usual trick to solve the problem is to iterate over a copy of the list… but once you're doing that, you've now got the worst of filtering and the worst of deleting-in-place at the same time.
The second function may be a little too clever, but let's look at it:
def returnPosition(self,team_name):
return [team._name for team in self._table].index(team_name)
First, I'm creating a list like the original one, but it's a list of just the names instead of the team objects. Again, let's decompose the list comprehension:
temp = []
for team in self._table:
temp.append(team._name)
Or try to translate it into English: This is a list of the team name of every team in the table.
Now, because this is a list of team names, I can use index(team_name) and it will find it. And, because the two lists have the same shape, I know that this is the right index to use in the original team list as well.
A much simpler solution would be to change _tables from a list of Teams into a dict mapping names to Teams. This is probably the most Pythonic solution—it looks a lot simpler than writing list comprehensions to do simple operations. (It's also probably the most efficient, but that's hardly relevant unless you have some truly gigantic leagues.) And then you don't even need returnPosition for anything. To do that:
def __init__(self):
self._table={}
def addTeam(self,name):
self._table[name]=Team(name)
def removeTeam(self,team_name):
del self._table[team_name]
def returnPosition(self,team_name):
return team_name
def updateLeague(self,team1_name1,team_name2,score1,score2):
if score1>score2:
self._table[team_name1].win()
self._table[team_name2].loss()
elif score1==score2:
self._table[team_name1].draw()
self._table[team_name2].draw()
elif score1<score2:
self._table[team_name1].loss()
self._table[team_name2].win()
Note that I've defined returnPosition to just return the team name itself as the position. If you think about it, dict keys are used exactly the same way as list indices, so this means any code someone wrote for the "old" API that required returnPosition will still work with the "new" API. (I probably wouldn't try to sell this to a teacher who assigned a problem that required us to use returnPosition, but for a real-life library where I wanted to make it easier for my 1.3 users to migrate to 2.0, I probably would.)
This only requires a few other changes. In displayList and saveList, you iterate over self._table.values() rather than self._table; in loadList, you change self._table.append(team) to self._table[a] = team. Speaking of loadList: You might want to consider renaming those local variables from a, b, c, and d to name, wins, losses, and draws.
A few other comments:
As kreativitea says in the comments, you should not create "private" variables and then add do-nothing accessor methods in Python. It's just more boilerplate that hides the real code, and one more thing you can get wrong with a silly typo that you'll spend hours debugging one day. Just have members named name, wins, losses, etc., and access them directly. (If someone told you that this is bad style because it doesn't let you replace the implementation in the future without changing the interface, that's only true in Java and C++, not in Python. If you ever need to replace the implementation, just read up on #property.)
You don't need print("""""")—and it's very easy to accidentally miscount the number of " characters. (Especially since some IDEs will actually be confused by this and think the multi-line string never ends.) Just do print().
You've got the same ending condition both in the while loop (while x!="q":) and in an internal break. You don't need it in both places. Either change it to while True:, or get rid of the break (just make options("q") do print("Goodbye"), so you don't need to special-case it at all inside the loop).
Whenever you have a long chain of elif statements, think about whether you can turn it into a dict of short functions. I'm not sure it's a good idea in this case, but it's always worth thinking about and making the explicit decision.
The last idea would look something like this:
def addTeam():
name=input("Enter the name of the team:")
l.addTeam(name)
def removeTeam():
teamToRemove=input("Enter the name of the team you want to remove:")
l.removeTeam(teamToRemove)
def recordGame():
team1=input("What is the name of the team?")
ans1=int(input("Enter the number of goals for the first team:"))
team2=input("What is the name of the team?")
ans2=int(input("Enter the number of goals for the second time:"))
l.updateLeague(team1,team2,ans1,ans2)
optionsdict = {
"a": addTeam,
"d": l.displayList,
"s": l.saveList,
"l": l.loadList,
"r": removeTeam,
"rec": recordGame,
}
def options(x):
func = optionsdict.get(x)
if func:
func()
As I said, I'm not sure it's actually clearer in this case, but it's worth considering.

Categories