I'm learning Python from a textbook and one of the exercises (there is no answer key available!) wants you to create 4 patterns of asterisks. This code creates the correct output.
As you can see it is 4 separate code blocks each with nested For Loops. My question is to whether it is possible to put it all into 1 For loop that is nested one level deeper (e.g. For group in range(4) > For row in range(10) > For asterisk in range(etc.)? Or since the output of each code block is different, is this most granular I could go?
Any insight is much appreciated!
for row in range(10):
for asterisk in range(row 1):
print('*', end='')
print()
print()
for row in range(10):
for asterisk in range(10 - row):
print('*', end='')
print()
print()
for row in range(10):
print(' ' * row, end='')
for asterisk in range(10 - row):
print('*', end='')
print()
print()
for row in range(10):
print(' ' * (9 - row), end='')
for asterisk in range(row 1):
print('*', end='')
print()
CodePudding user response:
Before trying to push the code into another level of for
loops, let's try to make a function that represents the needed logic for each part.
Start with one version of the code, and stub out the parts that change. Add a function header, and stub out the parameters for now.
def triangle(...):
for row in range(10):
...
for asterisk in range(...):
print('*', end='')
print()
Notice that I did not include the print
in between the triangles. That has separate logic (we want to do it every time except the first; not because of what kind of triangle we're drawing)
Before we try to figure out how to fill in the blanks, let's try to simplify the code by using what we already know. The existing code uses print(' ' * row, end='')
or print(' ' * (9 - row), end='')
to print a specific number of spaces (determined by row
). The for
loop is used to print a specific number of asterisks. Clearly, we can use the *
technique instead. So:
def triangle(...):
for row in range(10):
...
print('*' * ..., end='')
print()
And having done that, we see that we have a print
that deliberately suppresses the newline at the end, immediately followed by an empty print
used only to display a newline. That doesn't make a lot of sense. We could just:
def triangle(...):
for row in range(10):
...
print('*' * ...)
Much better.
Next: is our logic actually conditional? In the original versions of the code, we sometimes had a print
for some spaces, and other times not. But we can unify that: "not printing spaces" is the same thing as "printing zero spaces"; so really all we need is a rule that tells us how many spaces to print, and then we can use the same structure every time:
def triangle(...):
for row in range(10):
print(' ' * ..., end='')
print('*' * ...)
In fact, we can combine these print
s. We want to print two things one right after the other (i.e. with an empty sep
in between), and we want the default newline to be printed after that. So:
def triangle(...):
for row in range(10):
print(' ' * ..., '*' * ..., sep='')
Much better. On to the part where we actually fill in the blanks.
First off, what information do we have to pass in? We need to know two things: whether to right-align the triangle, and whether to print it upside-down or not. So those are our parameters:
def triangle(upside_down, right):
for row in range(10):
print(' ' * ..., '*' * ..., sep='')
What is the rule that tells us how many stars to print?
If the triangle is right-side-up, then we print row 1
stars. Otherwise we need 10 - row
stars. That matches the values that were passed to range
in the original code.
def triangle(upside_down, right):
for row in range(10):
stars = 10 - row if upside_down else row 1
print(' ' * ..., '*' * stars, sep='')
What is the rule that tells us how many spaces to print?
If the triangle is right-aligned, then we should print some spaces. How many? Well, for an upside-down triangle we used the row
number directly. For a right-side-up triangle we need 9 - row
stars. But we can simplify this, by thinking in terms of the remaining space after we account for the stars. We want a total of 10 stars plus spaces, so we can simply subtract 10 - stars
to get the space count.
If the triangle is left-aligned, of course, then we print zero spaces.
def triangle(upside_down, right):
for row in range(10):
stars = 10 - row if upside_down else row 1
spaces = 10 - stars if right else 0
print(' ' * spaces, '*' * stars, sep='')
Simple. Let's use it:
triangle(False, False)
print()
triangle(True, False)
print()
triangle(True, True)
print()
triangle(False, True)
By extracting the code first, it becomes much clearer how to iterate that code: we just need to change the arguments for the triangle
calls, and take care of the intermediate print
s. We have a clearly defined function that does one thing and has a reasonable name - in accordance with all the principles that a good programming book tries to teach you.
How do we repeatedly call a function with differing, constant (i.e. pre-calculated) arguments? Simple: we put those arguments in some data structure, and iterate over that. To print
after each triangle
except the last, our trick is to instead print
before each triangle
except the first, using some simple flag logic (a variable that tells us what to display - either a newline, or nothing - which changes unconditionally after displaying it).
So, for example:
# This tells us which values we will use to call `triangle` each time.
configurations = ((False, False), (True, False), (True, True), (False, True))
# This tells us what we will display before each `triangle`.
before_triangle = ''
for upside_down, right in configurations:
print(before_triangle, end='')
before_triangle = '\n'
triangle(upside_down, right)
CodePudding user response:
Here is one way to factor common logic out of your four for loops, and also to use string multiplication to eliminate the inner loops:
patterns = [
[(1,1), None],
[(-1,10), None],
[(-1,10), (1, 0)],
[(1,1), (-1,9)]
]
begun = False
for (sign, plus), leftPat in patterns:
print('', end = '\n' * begun)
begun = True
leftSign, leftPlus = leftPat if leftPat else (0,0)
for row in range(10):
print(' ' * (row * leftSign leftPlus), '*' * (row * sign plus), sep='')
Explanation:
- We populate
patterns
with a list of pairs of tuples (also pairs) that are asterisk params (required) and space params (optional, in other words: can beNone
) - We iterate over the patterns
- For each pattern, we use default values of (0,0) if the space params (
leftPat
) areNone
- We then use string multiplication to print the indicated number of spaces followed by the indicated number of asterisks for each row in the pattern
- There is additional logic using
begun
to print blank lines between each pattern
If an extra blank line at the end were acceptable, this could be simplified further to:
for (sign, plus), leftPat in patterns:
leftSign, leftPlus = leftPat if leftPat else (0,0)
for row in range(10):
print(' ' * (row * leftSign leftPlus), '*' * (row * sign plus), sep='')
print()
CodePudding user response:
Going over the n×n grid and letting a pattern expression decide whether to print an *
:
n = 10
h = n / 2
patterns = [
lambda: i >= j,
lambda: i j < n,
lambda: j >= i,
lambda: i j >= n-1,
]
for p in patterns:
for i in range(n):
for j in range(n):
print(' *'[p()], end='')
print()
print()
Output (Try it online!):
*
**
***
****
*****
******
*******
********
*********
**********
**********
*********
********
*******
******
*****
****
***
**
*
**********
*********
********
*******
******
*****
****
***
**
*
*
**
***
****
*****
******
*******
********
*********
**********
Bonus patterns:
lambda: max(i,j) >= h,
lambda: abs(i-h) abs(j-h) < h,
lambda: (i//2 | j//2) % 2,
Output:
*****
*****
*****
*****
*****
**********
**********
**********
**********
**********
*
***
*****
*******
*********
*******
*****
***
*
** **
** **
**********
**********
** **
** **
**********
**********
** **
** **