The exercise I am doing was given by a book that takes a dictionary argument and asks for me to give a return value of True or False. I am new to Python 3 and as a personal exercise for learning I want to convert all the conditions of a "valid dictionary as a chessboard into" a single return value. I haven't actually tested this code for errors as it isn't finished, but I did run it through an online validator I found https://extendsclass.com/python-tester.html.
I want to know how I can convert the following 2 code blocks into simple expressions to be used in the return statement in my function below, You can see below that I've converted most expressions into the return value already with "and" because "ALL expressions must == True"
for pieces in dictionary.values():
if all(pieces.startswith('b')) or \
all(pieces.startswith('w')):
return True
else:
return False
The above code block loops through the dictionary keys passed to function as "pieces", and compares each key individually to determine if it starts with a value of 'b' or 'w'. So if any key does not start with 'b' or 'w' the dictionary "chessboard" is false as it contains an improper piece. Ok I think I see an error in this I'm going to look into it and try to figure it out. Ok I noticed some errors in the above code that need to be addressed I am currently researching how to properly execute the above code.
for square in dictionary:
try:
if int(square[:0]) <= 8 and \
square[-1] <= 'h':
return True
else:
return False
except ValueError:
return False
I worked on the above code block a very long time and am still not sure that's the "best" implementation of what I want it to do. But I am still new and did my best. Anyway it slices the dictionary key and compares the first char in the key to make sure it isn't over 8 which is the maximum "valid range" if over valid range it returns false and anything not int is obviously automatically False and returned as such by the "exception".
Then it slices the dictionary key to get the last char of the dictionary key and compares it to <= 'h' as that is the valid range and anything over 'h' or not a valid type value will return as False. And then it compares the results of True/False "and" True/False with "and" because both conditions must be True.
Here is the function as it currently is with a test dictionary at the end:
def cBV(dic): # (c)hess(B)oard(V)alidator
Err = 'Error: Invalid Board -'
if not isinstance(dic, type({})):
raise TypeError('Object passed to cBV is not of type <class dict>')
chess_pieces = {'bk pieces': 0, 'wh pieces': 0,
'bk pawns': 0, 'wh pawns': 0}
# converts dictionary values into keys and assigns those keys "counn of values"
for squares, pieces in dic.items:
if pieces.startswith('b'): # alt if pieces[:0] == 'b':
if pieces.startswith('bpawn'): # alt if pieces == 'bpawn':
chess_pieces['bk pawns'] += 1
chess_pieces['bk pieces'] += 1
elif pieces.startswith('w'):
if pieces.startswith('wpawn'):
chess_pieces['wh pawns'] += 1
chess_pieces['wh pieces'] += 1
return 'wking' in dic.values() and \
'bking' in dic.values() and \
chess_pieces['bk pieces'] <= 16 and \
chess_pieces['wh pieces'] <= 16 and \
chess_pieces['bk pawns'] <= 8 and \
chess_pieces['wh pawns'] <= 8 and \
dict = {'8h': 'wking', '2c': 'bking', '3a': 'wpawn', '3b': 'wpawn', '3c': 'wpawn',
'3d': 'wpawn', '3e': 'wpawn', '3f': 'wpawn', '3g': 'wpawn', '3h': 'wpawn', '4b': 'wpawn'}
test = cBV(dict)
print('Is this a valid chessboard? ' + str(test))
What you have now is good, and you should feel proud - there are more fancy techniques for making things more concise, and you'll get more used to them as you wrap your head around how the various data structures work.
for pieces in dictionary.values()
if pieces.startswith('b') or \
pieces.startswith('w'):
return True
else:
return False
can be converted to the one-liner
return all(
piece.startswith('b') or piece.startswith('w')
for piece in dictionary.values()
)
which does a few things.
all()
function takes any iterable object, and returns True
if all of the values in that iterable are truthy. If even one of them is not, then it 'short-circuits' and returns False
instead.all()
, we give a "comprehension". A comprehension is essentially a one-line for
loop, of the form f(element) for element in iterable
: it performs whatever f(element)
is, for every element
in the iterable.
dictionary.values()
, which returns all of the values (but not the keys) in dictionary
(which, here, is 'wking', 'wpawn', ...
). In this case, these are strings.piece
is what we assign each element of, for each 'iteration' of the comprehension. It'll run for piece = 'wking'
, then for piece = 'wpawn'
, etc.piece.startswith('b') or piece.startswith('w')
is the function that we perform for each piece
. This outputs either True
or False
, depending on whether the conditions are met.[]
to have it output as a regular list. However, if you give a comprehension as an argument to a function like all()
, which is what we're doing here, then it will end up as a "generator", a slightly more efficient object that only calculates one object at a time. For our purposes, this isn't important.True
or False
, that all()
will consume.Similarly with your second code snippet. You have the basics down, but can be more concise. Your code:
def allSquaresAreInRange(dictionary):
for square in dictionary:
try:
if int(square[:0]) <= 8 and \
pieces[-1] <= 'h':
return True
else:
return False
except ValueError:
return False
can be turned into
def allSquaresAreInRange(dictionary):
try:
return all(
(1 <= int(row) <= 8) and ('a' <= col <= 'h')
for (row, col) in dictionary
)
except ValueError:
return False
Here we make use of a few things:
all()
, and as before, we use a comprehension. But this time, we iterate through dictionary
directly
dict
is functionally identical to iterating through dict.keys()
. So, we're iterating through the keys '8h', '2c', ...
(row, col) = '8h'
, for example, is functionally identical to row = '8h'[0]
and col = '8h'[1]
. In both cases, we're left with row = '8'
and col = 'h'
.ValueError
if the number of elements on either side is mismatched - for example, if the key has only one character, or only three characters. A byproduct of this is that row
and col
are guaranteed to be exactly one-character long strings, if that error doesn't happen.row
is between 1 and 8, and whether the col
is between A and H, using greater than/less than signs. This returns True
or False
, once again.
int()
on something that doesn't represent an integer will also throw a ValueError
.try
/except
blocks you came up with in yours, because they work just fine.Python has a bit of a culture surrounding it that prides 'efficiently-written' code. Which is to say, code that looks as fancy as possible, and follows Functional Programming paradigms. Comprehensions, as well as all()
and any()
, are a big part of that, and so they're probably the 'correct' solution for any problem which they are a solution for (if they can be written concisely).
Similarly, the snippet
if condition:
return True
else:
return False
can almost always be condensed to
return condition
(or return bool(condition)
, in the case that condition
deals with a value that has truthiness but isn't a boolean, such as None
or an empty list or string). If this is applicable, it's good practice (but again, it's not always applicable).
The most important thing, though, is that your code works the way you want it to, and that it's clear enough for you to come back to it a few months down the line and figure out what you were doing, and why you were doing it that way. There are some cases where things can be written as comprehensions but that makes them extremely complicated and unreadable - and in those cases, it's sometimes a good idea to not write them as comprehensions, and do it the more verbose way. Just keep that in mind as you're continuing to develop, and you'll do fine.