2. Numbers

Checking Whether a String Is a Valid Number

#-----------------------------
# The standard way of validating numbers is to convert them and catch
# an exception on failure

try:
    myfloat = float(mystr)
    print "is a decimal number" 
except TypeError:
    print "is not a decimal number"

try:
    myint = int(mystr)
    print "is an integer"
except TypeError:
    print "is not an integer"

# DON'T DO THIS. Explicit checking is prone to errors:
if mystr.isdigit():                         # Fails on "+4"
    print 'is a positive integer'   
else:
    print 'is not'

if re.match("[+-]?\d+$", mystr):            # Fails on "- 1" 
    print 'is an integer'           
else:
    print 'is not'

if re.match("-?(?:\d+(?:\.\d*)?|\.\d+)$", mystr):  # Opaque, and fails on "- 1"
    print 'is a decimal number'
else:
    print 'is not'

#-----------------------------

Comparing Floating-Point Numbers

#-----------------------------
# equal(num1, num2, accuracy) : returns true if num1 and num2 are
#   equal to accuracy number of decimal places

def equal(num1, num2, accuracy):
    return abs(num1 - num2) < 10**(-accuracy)
#-----------------------------
from __future__ import division  # use / for float div and // for int div

wage = 536                                      # $5.36/hour
week = 40 * wage                                # $214.40
print "One week's wage is: $%.2f" % (week/100)
#=> One week's wage is: $214.40
#-----------------------------

Rounding Floating-Point Numbers

#-----------------------------
rounded = round(num)            # rounds to integer
#-----------------------------
a = 0.255
b = "%.2f" % a
print "Unrounded: %f\nRounded: %s" % (a, b)
print "Unrounded: %f\nRounded: %.2f" % (a, a)
#=> Unrounded: 0.255000
#=> Rounded: 0.26
#=> Unrounded: 0.255000
#=> Rounded: 0.26
#-----------------------------
from math import floor, ceil

print "number\tint\tfloor\tceil"
a = [3.3, 3.5, 3.7, -3.3]
for n in a:
    print "% .1f\t% .1f\t% .1f\t% .1f" % (n, int(n), floor(n), ceil(n))
#=> number  int   floor ceil
#=>  3.3     3.0   3.0   4.0
#=>  3.5     3.0   3.0   4.0
#=>  3.7     3.0   3.0   4.0
#=> -3.3    -3.0  -4.0  -3.0
#-----------------------------

Converting Between Binary and Decimal

#-----------------------------
# To convert a string in any base up to base 36, use the optional arg to int():
num = int('0110110', 2)   # num is 54

# To convert an int to an string representation in another base, you could use
# <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/111286>:
import baseconvert 
def dec2bin(i):
    return baseconvert.baseconvert(i, baseconvert.BASE10, baseconvert.BASE2)

binstr = dec2bin(54)      # binstr is 110110
#-----------------------------

Operating on a Series of Integers

#-----------------------------
for i in range(x,y):
    pass # i is set to every integer from x to y, excluding y

for i in range(x, y, 7):
    pass # i is set to every integer from x to y, stepsize = 7

print "Infancy is:",
for i in range(0,3):
    print i,
print

print "Toddling is:",
for i in range(3,5):
    print i,
print

# DON'T DO THIS:
print "Childhood is:",
i = 5
while i <= 12:
    print i
    i += 1

#=> Infancy is: 0 1 2
#=> Toddling is: 3 4
#=> Childhood is: 5 6 7 8 9 10 11 12
#-----------------------------

Working with Roman Numerals

#-----------------------------
# See http://www.faqts.com/knowledge_base/view.phtml/aid/4442
# for a module that does this
#-----------------------------

Generating Random Numbers

#-----------------------------
import random          # use help(random) to see the (large) list of funcs

rand = random.randint(x, y)
#-----------------------------
rand = random.randint(25, 76)
print rand
#-----------------------------
elt = random.choice(mylist)
#-----------------------------
import string
chars = string.letters + string.digits + "!@$%^&*"
password = "".join([random.choice(chars) for i in range(8)])
#-----------------------------

Generating Different Random Numbers

#-----------------------------
# Changes the default RNG
random.seed()

# Or you can create independent RNGs
gen1 = random.Random(6)
gen2 = random.Random(6)
gen3 = random.Random(10)
a1, b1 = gen1.random(), gen1.random()
a2, b2 = gen2.random(), gen2.random()
a3, b3 = gen3.random(), gen3.random()
# a1 == a2 and b1 == b2
#-----------------------------

Making Numbers Even More Random

#-----------------------------
# see http://www.sbc.su.se/~per/crng/ or http://www.frohne.westhost.com/rv11reference.htm
#-----------------------------

Generating Biased Random Numbers

#-----------------------------
import random
mean = 25
sdev = 2
salary = random.gauss(mean, sdev)
print "You have been hired at %.2f" % salary
#-----------------------------

Doing Trigonometry in Degrees, not Radians

#-----------------------------
radians = math.radians(degrees)
degrees = math.degrees(radians)

# pre-2.3:
from __future__ import division
import math
def deg2rad(degrees):
    return (degrees / 180) * math.pi
def rad2deg(radians):
    return (radians / math.pi) * 180
#-----------------------------
# Use deg2rad instead of math.radians if you have pre-2.3 Python.
import math
def degree_sine(degrees):
    radians = math.radians(degrees)
    return math.sin(radians)
#-----------------------------

Calculating More Trigonometric Functions

#-----------------------------
import math

# DON'T DO THIS.  Use math.tan() instead.
def tan(theta):
    return math.sin(theta) / math.cos(theta)
#----------------
# NOTE: this sets y to 16331239353195370.0
try:
  y = math.tan(math.pi/2)
except ValueError:
  y = None
#-----------------------------

Taking Logarithms

#-----------------------------
import math
log_e = math.log(VALUE)
#-----------------------------
log_10 = math.log10(VALUE)
#-----------------------------
def log_base(base, value):
    return math.log(value) / math.log(base)
#-----------------------------
# log_base defined as above
answer = log_base(10, 10000)
print "log10(10,000) =", answer
#=> log10(10,000) = 4.0
#-----------------------------

Multiplying Matrices

#-----------------------------
# NOTE: must have NumPy installed.  See
#   http://www.pfdubois.com/numpy/

import Numeric
a = Numeric.array( ((3, 2, 3),
                    (5, 9, 8) ), "d")
b = Numeric.array( ((4, 7),
                    (9, 3),
                    (8, 1) ), "d")
c = Numeric.matrixmultiply(a, b)

print c
#=> [[  54.   30.]
#=>  [ 165.   70.]]

print a.shape, b.shape, c.shape
#=> (2, 3) (3, 2) (2, 2)
#-----------------------------

Using Complex Numbers

#-----------------------------
a = 3+5j
b = 2-2j
c = a * b
print "c =", c
#=> c = (16+4j)

print c.real, c.imag, c.conjugate()
#=> 16.0 4.0 (16-4j)
#-----------------------------
import cmath
print cmath.sqrt(3+4j)
#=> (2+1j)
#-----------------------------

Converting Between Octal and Hexadecimal

#-----------------------------
number = int(hexadecimal, 16)
number = int(octal, 8)
s = hex(number)
s = oct(number)

num = raw_input("Gimme a number in decimal, octal, or hex: ").rstrip()
if num.startswith("0x"):
    num = int(num[2:], 16)
elif num.startswith("0"):
    num = int(num[1:], 8)
else:
    num = int(num)
print "%(num)d %(num)x %(num)o\n" % { "num": num }
#-----------------------------

Putting Commas in Numbers

#-----------------------------
def commify(amount):
    amount = str(amount)
    firstcomma = len(amount)%3 or 3  # set to 3 if would make a leading comma
    first, rest = amount[:firstcomma], amount[firstcomma:]
    segments = [first] + [rest[i:i+3] for i in range(0, len(rest), 3)]
    return ",".join(segments)

print commify(12345678) 
#=> 12,345,678

# DON'T DO THIS. It works on 2.3+ only and is slower and less straightforward
# than the non-regex version above.
import re
def commify(amount):
    amount = str(amount)
    amount = amount[::-1]
    amount = re.sub(r"(\d\d\d)(?=\d)(?!\d*\.)", r"\1,", amount)
    return amount[::-1]

Printing Correct Plurals

# Printing Correct Plurals
#-----------------------------
def pluralise(value, root, singular="", plural="s"):
    if value == 1:
        return root + singular
    else:
        return root + plural

print "It took", duration, pluralise(duration, 'hour')

print "%d %s %s enough." % (duration, 
                            pluralise(duration, 'hour'), 
                            pluralise(duration, '', 'is', 'are'))
#-----------------------------
import re
def noun_plural(word):
    endings = [("ss", "sses"),
               ("([psc]h)", r"\1es"),
               ("z", "zes"),
               ("ff", "ffs"),
               ("f", "ves"),
               ("ey", "eys"),
               ("y", "ies"),
               ("ix", "ices"),
               ("([sx])", r"\1es"),
               ("", "s")]
    for singular, plural in endings:
        ret, found = re.subn("%s$"%singular, plural, word)
        if found:
            return ret
    
verb_singular = noun_plural;       # make function alias
#-----------------------------

Program: Calculating Prime Factors

# Program: Calculating Prime Factors
#-----------------------------
#% bigfact 8 9 96 2178
#8          2**3
#
#9          3**2
#
#96         2**5 3
#
#2178       2 3**2 11**2
#-----------------------------
#% bigfact 239322000000000000000000
#239322000000000000000000 2**19 3 5**18 39887 
#
#
#% bigfact 25000000000000000000000000
#25000000000000000000000000 2**24 5**26
#-----------------------------
import sys

def factorise(num):
    factors = {}
    orig = num
    print num, '\t',

    # we take advantage of the fact that (i +1)**2 = i**2 + 2*i +1
    i, sqi = 2, 4
    while sqi <= num:
        while not num%i:
            num /= i
            factors[i] = factors.get(i, 0) + 1

        sqi += 2*i + 1
        i += 1

    if num != 1 and num != orig:
        factors[num] = factors.get(num, 0) + 1

    if not factors:
        print "PRIME"

    for factor in sorted(factors):
        if factor:
            tmp = str(factor)
            if factors[factor]>1: tmp += "**" + str(factors[factor])
            print tmp,
    print
    
#--------
if __name__ == '__main__':
    if len(sys.argv) == 1:
        print "Usage:", sys.argv[0], " number [number, ]"
    else:
        for strnum in sys.argv[1:]:
            try:
                num = int(strnum)
                factorise(num)
            except ValueError:
                print strnum, "is not an integer"
#-----------------------------
# A more Pythonic variant (which separates calculation from printing):
def format_factor(base, exponent):
    if exponent > 1:
        return "%s**%s"%(base, exponent)
    return str(base)

def factorise(num):
    factors = {}
    orig = num

    # we take advantage of the fact that (i+1)**2 = i**2 + 2*i +1
    i, sqi = 2, 4
    while sqi <= num:
        while not num%i:
            num /= i
            factors[i] = factors.get(i, 0) + 1
        sqi += 2*i + 1
        i += 1

    if num not in (1, orig):
        factors[num] = factors.get(num, 0) + 1

    if not factors:
        return ["PRIME"]

    out = [format_factor(base, exponent)
           for base, exponent in sorted(factors.items())]
    return out

def print_factors(value):
    try:
        num = int(value)
        if num != float(value):
            raise ValueError
    except (ValueError, TypeError):
        raise ValueError("Can only factorise an integer")
    factors = factorise(num) 
    print num, "\t", " ".join(factors)