Home > Software engineering >  Is it possible to nest all 4 of these nested For loops into one For loop with an extra level?
Is it possible to nest all 4 of these nested For loops into one For loop with an extra level?

Time:07-11

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 prints. 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 prints. 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 be None)
  • We iterate over the patterns
  • For each pattern, we use default values of (0,0) if the space params (leftPat) are None
  • 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:

     *****
     *****
     *****
     *****
     *****
**********
**********
**********
**********
**********

          
     *    
    ***   
   *****  
  ******* 
 *********
  ******* 
   *****  
    ***   
     *    

  **  **  
  **  **  
**********
**********
  **  **  
  **  **  
**********
**********
  **  **  
  **  **  
  • Related