Logic, Tests, and the Nature of Truth


Topics:

  • if tests
  • and, or, and not logic

Introduction

So, we've now spent a few action-packed sessions learning about python, and we've learned quite a bit. We know what variables are, how to print them, and how to store and manipulate more complex data in more complex data structures. However, we're still missing one of the very essential components of programming: logic.

My goal, when I first started programming, was to make the computer do my work for me. I imagine that this is your goal, too -- instead of normalizing that microarray data or computing that crystal structure fit with a pocket calculator and a bucket of coffee, you want to be able to say 'Computer, do my work,' and then have it done. Of course, you'd have to say it a little more clearly than that -- for instance, to pick out over-expressed membrane proteins from microarray data, you could tell the computer that for each gene in the genome:

"If the protein is located in the membrane, tell me if the first column is much greater than the second column."

Q: What's the magic word there?
A: if

Indeed, the if statement is one of the most fundamental components of programming, and it is found in some form or another in every general-purpose programming language (and nearly all specialized ones, too). It simply tells the computer to execute a block of code if a given statement is true. If the statement is false, the code is skipped. Although the concept is simple, conditional statements like if allow your programs to make decisions on their own.

The Syntax of IF

In Python, we construct our if statements with this general form:


if <something that may or may not be true>:
  1. do these things
  2. also do these things

The key pieces of syntax here are the if, the <> that encompass the condition that we are evaluating, the : that signifies the end of the logical test, and the tab-indentation for the things to do below the first line of the statement.

We will discuss what makes things True in a minute, but for now we should focus on the syntax. Beyond the if and the colon, python will execute all of the following indented code. Indentation is the signal to the interpreter that this is a separate block of code under the control of the if statement.

Speaking of decision making, we could use this syntax to write a program that spares us the trouble of deciding what to eat each week:
#!/usr/bin/env python
checkingAccountBalance = 4
 
if checkingAccountBalance < 10:
     thisWeeksFood = 'ramen'
if checkingAccountBalance >= 10:
     thisWeeksFood = 'goodRamen'
 
print "We are eating %s this week" % (thisWeeksFood)
 
# similarly
if checkingAccountBalance == 15:
     thisWeeksFood = 'that particular brand of good ramen'
 
if checkingAccountBalance != 15:
     thisWeeksFood = 'certainly _not_ that brand!'
     # if statements can contain more than one command
     print 'absolutely not!'
 
 

Not bad for a start, but we'd like to clean that up a bit -- not only is that "checking_account_balance <= 10" a pile of unnecessary keystrokes, but it could also hurt us down the road. For instance, what if inflation pushed up the price of good ramen? We'd want to change out cutoff point-- to twelve bucks, for instance. However, if we changed one of the numbers but forgot to change the second, we'd have a bug in our code. By making our program simpler we can avoid such mistakes, and we can make things simpler by using python's else statement.
if checkingAccountBalance < 10:
     thisWeeksFood = 'ramen'
else:
     thisWeeksFood = 'goodRamen'

But what if there's a third option? or a fourth? This is taken care of by python's elif (short for "else if") statement.
if checkingAccountBalance < 10:
    thisWeeksFood = 'ramen'
elif checkingAccountBalance < 100:
    thisWeeksFood = 'good ramen'
elif checkingAccountBalance < 200:
    thisWeeksFood = 'better ramen'
else:
    thisWeeksFood = 'ramen that is truly profound in its goodness'

As a rule, it is good practice to account for all possibilities in your if, elif, else statements. In our case of the $10 budget, we initially cared only if we had $10 or more, and so this is a safe place to use the if, else construct to account for all possibilities. But what if we actually had a zero balance in our checking accounts, thus had to go without noodles entirely (don't worry, you can always scam cookies out of the seminar series)? Numerically speaking, we've accounted for all positive values with our above logic, but we've left an implicit assumption that having a non-positive balance still results in our eating ramen this week. The point: careful as you may be in planning your logic, there are often oversights that the computer's execution of logical will not overlook.

At least we know what's for dinner.

The Nature of Truth: Sneaking philosophy into computer science


But what is truth, anyway? Here we have a simple statement, "less than," and it's meaning is intuitive. But truth has a formal meaning in python, as it does in all programming languages. In short:

Any nonzero number, nonempty object, and the special object True are things Python believes to be True.
Zero numbers, empty objects, and the special objects None and False are things Python believes to be False.

Observe:
if 1:
    print "True!"
 
if 0:
    print "True!"
else:
    print "False!"
Of course, these operations are more useful with variables instead of straight numbers.
s = 0
if s:
    print "s is True!"
else:
    print "s is False!"
What if we want s to evaluate to true if s is zero?
s = 0
if s == 0:
    print "s is True!"
else:
    print "s is False!"
Non-empty strings are always true, no matter their content.
s = ""
if s:
    print "Your instructor has done something wrong"
else:
    print "'s' is surely not True, because 's' is empty."
 
 
s = "Python Class is *waaay* better than pipetting!"
if s:
    print "'%s' is True!" % s
else:
    print "'%s' is False!" % s
Python has abstract versions of truth and falsehood as well.
s = (2 + 2) == 4
print s
 
if s:
    print "True!"
else:
    print "False!"
 
 
s = (2 + 2) == 5
print s
 
if s:
    print "True!"
else:
    print "False!"
In Python, we also have a third notion, None. This is rarer, and it evaluates to falsehood when in an if statement. We'll see more of it later when we talk about writing our own functions.
s = None
if s:
    print "nothing is true!"
else:
    print "nothing is false!"

and, or, (and) not

Finally, what if we want to combine two or more statements in a single line? Boolean operators can help us out. and will only return True if both statements are True, while or will return True if either statement is True. not returns the inverse of the logical value of the statement given.
true_statement = 'truth itself'  # non-empty strings are true
false_statement = ''  # empty strings are false
 
if true_statement and false_statement:
    print "This should not print."
else:
    print "This should print"
 
if true_statement or false_statement:
    print "This should print"
 
 
if not false_statement:
    print "This should print"
 
if not (true_statement and false_statement):
    print "This should print"
 
However, and and or are a little more complicated than, for instance, the > and == operators that we're used to seeing in if statements. Let's look at and first:
a = 5 < 3
print a
 
b = 1 and 1
print b
 
c = 1 and 0
print c
 
d = 1 and 2
print d
 
e = 2 and 1
print e
 
f = 0 and 2
print f
 
The and statement requires that both values in the expression evaluate to True values. But instead of returning True or False, the and statement continues evaluating the logical expression until either it reaches the end of the expression, or reaches a False value, at which point it returns the last value it evaluated.

I think about this by imagining the computer as looking at each statement in turn, left to right, and being maximally lazy about the number of statements it wants to look at and maximally forgetful about the number of statements it has to remember. For instance, when it's given the 'd = 1 and 2' statement, it first looks at 1, figures out that it's True, and then it checks the 2 -- only remembering the 2, it returns that. It's the reverse case with the 'e = 2 and 1' statement: as it looks at the 1 last, and so returns 1. And finally, with the 'f = 0 and 2' statement, it knows that the 'and' can't possibly be true if the first part is false, so it just returns the only thing that it has seen -- the 0.

or works in a similar manner, evaluating the values in the expression until it either reaches the end of the expression or reaches a True value, at which point it returns the last value it evaluated.
g = 0 or 1
print g
 
h = 1 or 0
print h
 
i = 1 or 2
print i
 
j = 2 or 1
print j
 
In the first case, the computer needs to see the second variable to evaluate the or, so it returns the second variable. In the second through fourth cases, it knows that the or should be true after only seeing the first variable, and so it returns that.

Order of Operations with Logical Tests

We can make expressions of logic arbitrarily complicated, and so it's good to know where on the Great Chain of Operations these logical operators fall.

a = 1 and 2 + 5
print a
 
a = (0 and 2) + 5
print a
 
a = not (0 and 2) + 5
print a
 
a = 5 and not (0 and 2)
print a


Exercises


1. Some more truth exercises. Use the format:
query = <fill in blank>
if query:
    print 'Query is true'
else:
    print 'Query is false'
Try the following things in the blank. Are they True? If you don't see exactly why they are True or False, please ask a TA.

a) "" (the empty string)

b) []

c) {}

d) (a list containing an empty list)

e) 0

f) [0]

g) [0][0] (also-- what is this? Print out the value of [0][0] and [1][0] before doing the truth test)

h) [0]

2. Have the user input two numbers. If the numbers are equal, print the number back.

3. Modify your solution of Exercise #2 such that if the numbers are not equal, print the first number that the user entered back. Use the else statement.

4. Modify your solution of Exercise #3 such that if the numbers are not equal, print the smaller number. Use the elif statement.

5. Write your first gene-finding program: have the user enter a string of six nucleotides. If the length of the string is not six, tell the user to input another string. If it is, then create the four three-base substrings of this string. If any of these substrings is the sequence 'ATG', print the position of that substring (If 'ATG' appears multiple times, print only the location of the first instance of 'ATG'). If none of the substrings match 'ATG', print -1.

Your gene finder isn't very good at finding genes yet, but it is good at motivating some of the things that you'll be learning about in the next two lectures: loops, which perform a single task over and over and over again, and python's text processing capabilities, which reduce the bulk of this program to a single line.

6. Write your first fasta parser: from a series of user inputs, you wish to create a dictionary keyed by gene IDs with values corresponding to their sequence. A fasta file has the following format:
>gene1
ATAGCAGTTAGC
TTAGCAGCAGTT
ATAGCGCA
>gene2
ATGACGACGATT
TTGACGACTAGG
ACAGCC
>gene3
AGATGCCCCCTT
...
While you will learn about handling files soon, for now, assume that this file is being typed in by the user -- their first input will be '>gene1', their second will be 'ATAGCAGTTAGC', and so on. Ask the user for input ten times. For each line input, you will have to determine whether it is a gene name or a sequence and act accordingly.

Your dictionary should have keys 'gene1', 'gene2', and 'gene3' (note the absence of the '>' symbol) and the sequences of those genes as values.

7. Replace "X" in this code with a statement that ensures that if the user entered "1", a "0" is printed, and if they entered a "0", a "1" is printed. Don't worry about other numbers.
s = raw_input("Enter a number: ")
s = int(s)
t = X
print t
 

Solutions

1.

# "" is False
 
# [] is False
 
# {} is False
 
# [ [] ] is True, because the list isn't empty, it has a list in it!
 
# 0 is False
 
# [0]
 
# [0][0] is False, because [0] is a list, and [0][0] is the first position of the list, a.k.a., 0
 
# [ [] ][0] is False, because the 0th element of the list is an empty list.
 
2.
a = raw_input("Enter a number:  ")
 
b = raw_input("Enter another number:  ")
 
if a == b:
    print "These numbers are the same!"
 
3.

a = raw_input("Enter a number:  ")
 
b = raw_input("Enter another number:  ")
 
if a == b:
    print "These numbers are the same!"
else:
    print a, "is the first number you entered,  but these numbers are not equal!"
 
4.

a = raw_input("Enter a number:  ")
 
b = raw_input("Enter another number:  ")
 
if a == b:
    print "These numbers are the same!"
elif a < b:
    print a, "is the smaller of these numbers."
else:
    print b, "is the smaller of these numbers."
 
 

5.

hexamer = raw_input("Please enter a six-nucleotide sequence in all capital letters:  ")
if len(hexamer) != 6:
    print "Oh, hrmmmm... I'm fairly certain that sequence isn't of length 6."
    hexamer = raw_input("Please enter a six-nucleotide sequence in all capital letters:  ")
 
start = 0
stop = 3
 
if hexamer[start:stop] == 'ATG':
    print start, "is the position of the first Met codon."
else:
    start += 1
    stop += 1
 
if hexamer[start:stop] == 'ATG':
    print start, "is the position of the first Met codon."
else:
    start += 1
    stop += 1
 
if hexamer[start:stop] == 'ATG':
    print start, "is the position of the first Met codon."
else:
    start += 1
    stop += 1
if hexamer[start:stop] == 'ATG':
    print start, "is the position of the first Met codon."
else:
    print "-1"
 

6.

# a dict for genes
genes = {}
 
#1
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#2
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#3
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#4
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#5
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#6
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#7
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#8
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#9
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
#10
 
line = raw_input("Enter a line:  ")
 
if line[0] == ">":
    gene = line[1:]
    genes[gene] = ''
else:
    genes[gene] += line
 
 
print genes
 

7.

s = raw_input("Enter a number: ")
s = int(s)
t = int(not s)
print t