Should I use .find() or index?

A few questions on this one:

  1. Why use find instead of .index?

  2. I tried the following boolean to achieve the return word if start or end are not in the string. It seems to always come back true. But if I remove the or condition and just use if start not in word then returns false and moves on.

def substring_between_letters(word,start,end):
  if start or end not in word:
    return print(word)
  else:
    startPoint = word.index(start)
    endPoint = word.index(end)
    return print(word[startPoint+1:endPoint])
#Uncomment these function calls to test your function:
print(substring_between_letters("apple", "p", "e"))
  1. Why does the following code print “None” as well as the expected result?
def substring_between_letters(word, start, end):
  start_point = word.find(start)
  end_point = word.find(end)
  print(start_point, end_point)
  if start_point == -1 or end_point == -1:
    return print(word) 
  else: 
    return print(word[start_point+1 : end_point])
# Uncomment these function calls to test your tip function:
print(substring_between_letters("apple", "p", "e"))
# should print "pl"
print(substring_between_letters("apple", "p", "c"))
# should print "apple"
  1. They are the same, except that str.find() returns -1 if the string is not found, and is only available for strings, while obj.index() throws an error if the argument is not found, and is available to lists and other subscriptable iterables.

  2. not takes precedence over or, so the expression is parsed as:
    if start or (end not in word):, and assuming that start is anything with a truth value of True (i.e. is not False, 0 (zero), an empty container (list,string, tuple, dictionary), or any expression that evalates to one of those), it will further parse to if True or anything_else:, which will always return True.

  3. The function print() evaluates the expression within its parentheses, and then sends the resulting value as a stream of text to the default i/o device, generally the screen. But (as with every Python function having no explicit return starement), it returns the value None.
    Remember:

  • We print() values that we want to see

  • We return values that Python needs to use (to assign to a variable, perhaps, or to send to another function.)

4 Likes

So basically it only evaluates one statement at a time. If I wrote

if start or end in word

Would it always evaluate to True?

I.e. ‘start’ is never evaluated as whether or it is in word.

For instance:

if start not in word or end not in word:

appears to work and I’m guessing:

if start in word or end in word: 

would also work.

but would the following code break because of the “not” priority?

if start not in word or end in word:

So this is because there are two print statements in the function one of which will always be false and thereby return None?

Thanks!!!

The second operand will not be evaluated unless start cannot be coerced to True.


1 or 0     => 1
'a' or ''  => a
{} or []   => [] 
[] or {}   => {}

Correct.

This would be a case for AND where both operands must satisfy their conditional.


Membership is not precluded by truthiness…

>>> x = [(), ()]
>>> () in x
True
>>> 

This is a gotcha in strings…

>>> '' in 'string'
True
>>> 

In fact there are seven empty strings in that string.

>>> 'string'.count('')
7
>>> 'string'.find('')
0
>>>

This suggests the first test might be to test against the alpha set.

start.isalpha()
end.isalpha()

It would make logical sense when the end string is not in the target for the return to begin at the start string, assuming it is in the target.

pple
1 Like

No!

in and not in are of equal precedence (note that the table in this link shows precedence ordered from least to greatest), so consequently the expression is parsed:

if (start not in word) or (end in word):
…and then evaluated from left to right.

So if start not in word returns True, evaluation ceases, and the if block is executed. If that expression returns False, then end in word is evaluated. If it returns True, the if condition returns True and the if block is executed. If end in word returns False, the if block is skipped.

  • Remember that the expression x or y will behave as you expect only if both x and y are expressions that are guaranteed to return True or False.

  • In general, is is best not to use or as a type of iterable (or a shopping list): if (x or y or z) in str: It is much safer to use if x in str or y in str or z in str:

( … all of the above: ditto with and.)


The problem with
return print(word[start_point+1 : end_point])

… is that is evaluated like this:
return print(something)

  • So to know what to return, Python must evaluate the expression following return: print(something).

  • That means that the expression something is evaluated, and its value returned to the function print().

  • That function does its job, and the value returned by something is sent to the screen as a stream of text. The function print() has executed (printed to the screen), but it returns None. That is its nature: it prints, it does not return (and, in Python, that means it returns None.)

  • So the expression return print(word[start_point+1 : end_point]) ultimately parses to

  • return None (although something is printed in the meantime).

2 Likes

A post was split to a new topic: The nice thing about slices

I just have one doubt in this exercise:

def substring_between_letters(word,start,end):
  if start in word and end in word:
    substring = word[word.find(start):word.find(end)]
    return substring
  else:
    return word

here was my code. It got said it was wrong because in a-p-p-l-e, it got the first ‘p’ and then returned ‘ppl’ . So, should my code return only what is in the middle of the start and end, without including start nor end?

1 Like

Hello @lordtmnz! Yes, only the letters between start and end should be returned. Including the start or end characters would be incorrect.

I would change the title of this code challenge to “find letters within word.” Sub string implies an oppressive hierarchy in code lacking legal awareness.

Hi, I choose to use .find() for the easy solution.

My solution in below:

def substring_between_letters(word, start, end):
  return word if (start not in word or end not in word) else word[word.find(start):word.rfind(end)-1]

I really like this approach.

It’s crafty how you avoided using unnecessary variable declarations. Thanks for sharing

It’s difficult to verify that one’s code works if we don’t even have a link to the exercise. Somebody please post a link.

https://www.codecademy.com/courses/learn-python-3/lessons/python-functions-strings-cc/exercises/substring-between-letters

1 Like

please anyone advise why my code did not run as output expected?

def substring_between_letters(word, start, end):
 if start and end in word:
    start_ind = word.find(start)
    end_ind = word.find(end)
    new_word = word [start_ind+1:end_ind]
    return new_word
  else:
  	return word
 

The order in which Python evaluates this line is different from what you may think. in will be evaluated before and. This means you are essentially evaluating if start and (end in word):. Make sure you are checking if both start and end are in word.

Solution
if start in word and end in word:

You can find more about operator precedence here.

Thank you Victoria! You solved my problem and i guess I could finally have a good sleep tonight!

1 Like

This is one of the approaches i used:

def substring_between_letters(word, start, end):

if start in word and end in word and word.index(start) < word.index(end):

return word[word.index(start)+1:word.index(end)]

else:

return word
1 Like

I used almost the same

def substring_between_letters(word, start, end):
  if start in word and end in word:
    substring_of_word = word[word.find(start) + 1:word.find(end)]
    return substring_of_word
  else:
    return word
1 Like

Hi people!
I made it this way

#I dont know why
if start or end not in word
doesnt work (if someone can comment me why),

but this way it works properly

def substring_between_letters(word, start, end):
if start not in word:
return word
elif end not in word:
return word
else:
return word[word.find(start)+1:word.find(end)]

print(substring_between_letters(“apple”, “p”, “e”))
#returns “pl”
print(substring_between_letters(“apple”, “a”, “k”))
#returns “apple”
print(substring_between_letters(“vbpyuxchgbn”, “p”, “c”))
#returns “yux”
print(substring_between_letters(“vbpyuxchgbn”, “a”, “c”))
#returns “vbpyuxchgbn”
print(substring_between_letters(“vbpyuxchgbn”, “p”, “a”))
#returns “vbpyuxchgbn”

The problem is with operator precedence. or, not, and other operators in Python follow a set hierarchy. not has higher precedence than or. This basically means that not will be evaluated before or.

So, if your expression is if start or end not in word, it is evaluated as if start or (end not in word) (I’ve added the parentheses to show that not is evaluated first). Because start likely holds a non-empty string, it will evaluate to True (if it were an empty string, it would be False). This means that your expression will almost always be True since, if start is True, then the expression is satisfied as or only needs one of the two things it is comparing to be true.

Note: in future posts, please post your code formatted using the </> button.