Home > front end >  Udacity CS101: Lesson 8 - Problem Set (Optional 2)
Udacity CS101: Lesson 8 - Problem Set (Optional 2)

Time:12-08

Q1 of this problem set is the following:

Write a Python procedure fix_machine to take 2 string inputs and returns the 2nd input string as the output if all of its characters can be found in the 1st input string and "Give me something that's not useless next time." if it's impossible. Letters that are present in the 1st input string may be used as many times as necessary to create the 2nd string (you don't need to keep track of repeat usage).

For example:

print fix_machine('UdaciousUdacitee', 'Udacity') should output "Give me something that's not useless next time."

and:

print fix_machine('buy me dat Unicorn', 'Udacity') should output 'Udacity'

This is the solution I came up with by myself:

def fix_machine(debris, product):
    i = -1
    while True:
        i = i  1
        if debris.find(product[i]) == -1:
            return "Give me something that's not useless next time."
            break
        word = product[0:i 1]
        if word == product:
            break
    return word

I found this other sample solution online:

def fix_machine(debris, product):
    x = 0
    while x < len(product):
        if debris.find(product[x]) == -1:
            return "Give me something that's not useless next time."
        x  = 1
    return product

My code executes correctly, but I was wondering if it makes sense the way I'm using the break function. Any recommendation about how to better understand while-loops and optimize them?

CodePudding user response:

The two are roughly equivalent in terms of what they do. You are looping until you break, whereas the second solution always loops to what it knows to be the maximum number of loops needed (unless it finds the failure case) - the length of the product argument. Both will run in the same O(n) amount of time, so while you have some awkwardness in your code it's not practically different.

There are other approaches to go about this that are clearer to understand. For instance, since you don't care about repeat variables, it might be easier to take your product as input to a set:

required_characters = {c for c in product}  # puts all characters into a set, dropping repeats (because sets can only have a given value once)
for c in required_characters:
    if c not in debris:
        return "Give me something that's not useless next time."
return product

The functional difference here is that you are looping through product once, and can stop looping through debris the moment you find all the characters. But this also runs in O(n) - which is as fast as you can actually do this. The real question is what is clearest to you, the programmer, and clearest to whomever reads your code.

In the comments, @alani notes that you can also do something like this:

if set(product) <= set(debris):
    return product
return "Give me..."

This does something similar, using set comparison. The slight downside here is that you're putting both inputs into sets (an O(n) operation), which means you're guaranteed the maximum runtime - nothing will quit early if it knows it is a condition that can't be satisfied. Still, it is even more concise and therefore clear. For reasonable inputs a totally valid approach.

CodePudding user response:

while loops can be used for any type of looping but for situations where you're iterating over a collection (like the letters in a word) a for loop is much more appropriate. Generally a while loop is more useful when you don't know in advance how many times the loop will execute.

Here's a sample of iterating through both words looking for a letter in product that doesn't appear in debris:

NOT_FOUND_MSG = "Give me something that's not useless next time."

def fix_machine(debris, product):
    for p_char in product:
        found = False
        for d_char in debris:
            if p_char == d_char:
                found = True
                break
        if not found:
            return NOT_FOUND_MSG
    return product

We can clarify (and maybe optimize) by using in to check if p_char is in debris without explicitly iterating over it:

def fix_machine(debris, product):
    for p_char in product:
        if p_char not in debris:
            return NOT_FOUND_MSG
    return product

Let's trim it down some more by using an alternate form of the for loop:

def fix_machine(debris, product):
    if any(p_char not in debris for p_char in product):
        return NOT_FOUND_MSG
    return product

Finally an alternative which eliminates explicit loops altogether by taking advantage of the properties of a set:

def fix_machine(debris, product):
    return product if set(product).issubset(debris) else NOT_FOUND_MSG

CodePudding user response:

I'm going to answer this under the assumption this is part of an exercise on while loops.

Your solution was a good attempt, but there are a few things that you can still improve. I'm going to walk through your code and point them out.

def fix_machine(debris, product):
    i = -1        # THING 1
    while True:
        i = i  1  # ALSO THING 1
        if debris.find(product[i]) == -1:
            return "Give me something that's not useless next time."
            break # THING 2
        word = product[0:i 1] # THING 3
        if word == product:
            break
    return word

Thing 1: Initializing your index variable like this may work, but it isn't very clean. Instead you should do what the example you provided did and initialize to zero then increment at the end of your loop. This is easier to read and better practice overall.

Thing 2: This break statement will never be reached. The return statement will end your function (and by extension, the loop).

Thing 3: This is a clever idea, but a better idea would be to break once you've iterated over the entirety of the product. That way you don't have to check for equality every iteration.

If you fix all of these you'll notice you end up with the second example:

def fix_machine(debris, product):
    x = 0 #THING 1 (Initialize to zero)
    while x < len(product): #THING 3 (stop at end of product)
        if debris.find(product[x]) == -1:
            return "Give me something that's not useless next time." # THING 2 (no break)
        x  = 1 # THING 1(INCREMENT AT END)
    return product
  • Related