'''
This code was written in 15-113 lecture on Tue 31-Jan and Thu 2-Feb.
It is only for demonstrational purposes, and may contain
dubious style and even an occasional bug.
'''

def f(A, B):
    return (A and not B) or (B and not A)

def g(A, B, C):
    return f(A, C) and not f(A, B)

import inspect

def getArgCount(f):
    # This is gross but brings great joy to me
    # as an engineer even while it makes me ill.
    # Of course, we won't use this code (see getParameters instead).
    source = inspect.getsource(f)
    firstLine = source.splitlines()[0]
    if '()' in firstLine: return 0
    return 1 + firstLine.count(',')

def getParameters(f):
    parameters = list(inspect.signature(f).parameters)
    return parameters

# Approach #1: count in base 2 from 0 to (2**N - 1)
def getBooleanAssignmentsOfLengthN(N):
    result = [ ]
    for i in range(2**N):
        bits = [ ]
        while len(bits) < N:
            bits.append(i%2)
            i //= 2
        result.append(tuple(reversed(bits)))
    return result

# Approach #2: Use recursion
def getBooleanAssignmentsOfLengthN(N):
    if N == 1:
        return [ [0], [1] ]
    else:
        shorterResult = getBooleanAssignmentsOfLengthN(N-1)
        zeroFirst = [[0] + v for v in shorterResult]
        oneFirst  = [[1] + v for v in shorterResult]
        return zeroFirst + oneFirst

# Approach #3: Use itertools.product
from itertools import product
def getBooleanAssignmentsOfLengthN(N):
    return list(product([0, 1], repeat=N))

def makeTruthTable(f):
    parms = getParameters(f)
    booleanAssignments = getBooleanAssignmentsOfLengthN(len(parms))#
    # print labels:
    for parm in parms:
        print(parm, end=' ')
    print(f.__name__)
    # print rows:
    for booleans in booleanAssignments:
        result = int(f(*booleans))
        row = list(booleans) + [result]
        print(' '.join([str(v) for v in row]))

makeTruthTable(f)
print()
makeTruthTable(g)
