Python Solutions to [https://prof.ti.bfh.ch/hew1/informatik3/prolog/p-99/ 99 Prolog Prolems].
Index TableOfContents(2)
Problems 1-6
André Roberge has a zip file with solutions to the first six problems, in Crunchy format: [http://crunchy.googlecode.com/files/first_6_of_99_problems.zip First six]
Problem 7: Flatten a nested list structure
Based on the standard library documentation:
from itertools import chain def flatten(listOfLists): return list(chain(*listOfLists))
The suggested solution does not work for a list like the following:
a_list = [0, 1, [2, 3], 4, 5, [6, 7]]
as the argument name tries to imply, it only works for a list of lists, not a generic list of variously and mixedly nested lists and items. Here's a more general solution using the simple recursive approach:
def flatten(nestedList): def aux(listOrItem): if isinstance(listOrItem, list): for elem in listOrItem: for item in aux(elem): yield item else: yield listOrItem return list(aux(nestedList))
This problem is also a good example of "recursion elimination": explicitly maintain a LIFO stack of what sublists are being expanded so as to avoid actual recursion. The rec-elim approach is usually faster and avoids issues with recursion depth limits. Here's a version that works when it's OK to dismantle the input argument -- for variety, I have it build the result into another list by calls to .append, instead of using yield in an auxliary generator and calling list() on it.
def flatten(nestedList): result = [] stack = [nestedList] while stack: if isinstance(stack[-1], list): try: stack.append(stack[-1].pop(0)) except IndexError: stack.pop() # remove now-empty sublist else: result.append(stack.pop()) return result
If you're not allowed to dismantle the input argument, you can take a preliminary copy.deepcopy of it as the initial item in the stack, or you can "pay as you go" by doing shallow copies "at the last minute" when needed. Here's an example of the latter approach, with other little variants. Here, stack is always a list of non-empty sublists which are shallow copies of sublists from the initial argument (and so the sublists on the stack can always be dismantled with no problems) while leaves (non-list subitems) are always immediately appended to the result (this, btw, builds up the result in a reversed way, so a call to result.reverse becomes necessary).
def flatten(nestedList): result = [] if not nestedList: return result stack = [list(nestedList)] while stack: current = stack.pop() next = current.pop() if current: stack.append(current) if isinstance(next, list): if next: stack.append(list(next)) else: result.append(next) result.reverse() return result
Problem 8: Eliminate consecutive duplicates of list elements
from itertools import groupby def compress(alist): return [key for key, group in groupby(alist)]
Problem 9: Pack consecutive duplicates of list elements into sublists
from itertools import groupby def pack(alist): return [list(group) for key, group in groupby(alist)]
Problem 10: Run-length encoding of a list
from itertools import groupby def encode(alist): return [[len(list(group)), key] for key, group in groupby(alist)]
Problem 11: Modified run-length encoding
def encode_modified(alist): def aux(lg): if len(lg)>1: return [len(lg), lg[0]] else: return lg[0] return [aux(list(group)) for key, group in groupby(alist)]
Problem 12: Decode a run-length encoded list
def decode(alist): def aux(g): if isinstance(g, list): return [(g[1], range(g[0]))] else: return [(g, [0])] return [x for g in alist for x, R in aux(g) for i in R]
Problem 13: Run-length encoding of a list (direct solution)
def encode_direct(alist): def aux(k, g): l = sum(1 for x in g) if l>1: return [l, k] else: return k return [aux(key, group) for key, group in groupby(alist)]
Problem 14: Duplicate the elements of a list
def dupli(L): return [x for x in L for i in (1,2)]
Problem 15: Duplicate the elements of a list a given number of times
def dupli(L, N): return [x for x in L for i in range(N)]
Problem 16: Drop every N'th element from a list
def drop(L, N): return [x for i,x in enumerate(L) if i%N != N-1]
Problem 17: Split a list into two parts; the length of the first part is given
def split(L, N): return L[:N], L[N:]
Problem 18: Extract a slice from a list
Given two indices, I and K, the slice is the list containing the elements between the I'th and K'th element of the original list (both limits included). Start counting the elements with 1.
def slice(L, I, K): return L[I-1:K]
Problem 19: Rotate a list N places to the left
def rotate(L, N): return L[N:] + L[:N]
Problem 20: Remove the K'th element from a list
def remove_at(L, N): return L[N-1], L[:N-1] + L[N:]
Problem 21: Insert an element at a given position into a list
def insert_at(x, L, N): return L[:N-1]+[x]+L[N-1:]
Problem 22: Create a list containing all integers within a given range
def irange(I, J): return range(I, J+1)
Problem 23: Extract a given number of randomly selected elements from a list
import random def rnd_select(L, N): return random.sample(L, N)
Problem 24: Lotto: Draw N different random numbers from the set 1
import random def rnd_select(N, M): return random.sample(range(1, M+1), N)
Problem 25: Generate a random permutation of the elements of a list
import random def rnd_permu(L): return random.sample(L, len(L))
Problem 26: Generate the combinations of K distinct objects chosen from the N elements of a list
def combination(K, L): if K<=0: yield [] return for i in range(len(L)): thisone = L[i:i+1] for another in combination(K-1, L[i+1:]): yield thisone + another
Problem 27: Group the elements of a set into disjoint subsets
A natural recursive approach requires "temporarily modifying" certain things (the main list, the list of sublists, the list of counts of remaining lengths desired in the sublists); one way to express this is by the `with' statement and the "resource allocation is initialization" (RAII) idiom it enables...:
from __future__ import with_statement import contextlib import itertools def group(alist, glens): # entries in glens are ints >0 summing to len(alist) assert all(g>0 for g in glens) assert sum(glens) == len(alist) # return the generator made by an auxliary function return _g(alist, glens, [ [] for g in glens ]) # # helpers: with-statement contexts for RAII idioms # @contextlib.contextmanager def popping(L): item = L.pop() yield item L.append(item) @contextlib.contextmanager def appending(L, item): L.append(item) yield L L.pop() @contextlib.contextmanager def decrementing(L, index): L[index] -= 1 yield L L[index] += 1 # # helper: auxiliary recursive generator function # def _g(L, rls, grps): if sum(rls) == 0: yield list(list(grp) for grp in grps) return with popping(L) as item: for i, (rl, grp) in enumerate(itertools.izip(rls, grps)): if rl > 0: with appending(grp, item): with decrementing(rls, i): for filled in _g(L, rls, grps): yield filled
However, the Zen of Python says that "flat is better than nested", and, of course, we can express _g in a much flatter way by giving up the nesting, e.g. as follows:
# # helper: auxiliary recursive generator function # def _g(L, rls, grps): if sum(rls) == 0: yield list(list(grp) for grp in grps) return item = L.pop() for i, (rl, grp) in enumerate(itertools.izip(rls, grps)): if rl == 0: continue grp.append(item) rls[i] -= 1 for filled in _g(L, rls, grps): yield filled rls[i] += 1 grp.pop() L.append(item)
Which is more readable? "Ai posteri l'ardua sentenza..."!-)
Problem 28: Sorting a list of lists according to length of sublists
def lsort(L): return sorted(L, key=len) from collections import defaultdict def lfsort(L): lencounts = defaultdict(int) for sublist in L: lencounts[len(sublist)] += 1 def bylenfreq(sublist): return lencounts[len(sublist)] return sorted(L, key=bylenfreq)
Note: there is no problem 29 in the original problem set
Note: there is no problem 30 in the original problem set
Problem 31: Determine whether a given integer number is prime
Simplest approach: generate all primes, stop when the number N under test equals a prime, or is divisible by it without being equal, or when no higher prime is of interest because we've checked all primes <= sqrt(N).
import itertools def erat2(): # from Python Cookbook, 2nd Edition, recipe 18.10 D = {} yield 2 for q in itertools.islice(itertools.count(3), 0, None, 2): p = D.pop(q, None) if p is None: D[q*q] = q yield q else: x = p + q while x in D or not (x&1): x += p D[x] = p def is_prime(N): for p in erat2(): if N == p: return True elif p*p > N: return True elif N%p == 0: return False
Note: problems 32-45 still to be done (PLEASE edit this place-holder as you do more problems!)
Problem 46: Print a truth table for a logical expression of two variables
def table(expr): """ print truth table for logical expression >>> table('and(A,or(A,B))') A B and(A,or(A,B)) True True True True False True False True False False False False """ # uppercase functions to avoid name clashes with # python reserved words def AND(a,b): return a and b def NAND(a,b): return not (a and b) def OR(a,b): return a or b def NOR(a,b): return not (a or b) def XOR(a,b): return a ^ b def EQU(a,b): return not (a ^ b) def IMP(a,b): return b if a else True # print a nice header format = "%-5s %-5s %-5s" print format % ('A','B',expr) # convert the expression to uppercase and # compile it for later 'eval' call expr = compile(expr.upper(),'<expression>','eval') for A in (True,False): for B in (True, False): # locals() provides the environment for # evaluating the compiled expr, and # includes A, B, and the logical functions # defined above (AND, NAND, ...) print format % (A, B, eval(expr,locals()))
Problem 47: Print a truth table for an infix logical expression of two variables
def table( expr ): """ P47: Print a truth table for an infix logical expression >>> table('A and not B') A B A and not B True True False True False True False True False False False False >>> table('not(A imp B)') A B not(A imp B) True True False True False True False True False False False False """ # convert infix expression to prefix (function call) form def toPrefix( expr ): from re import finditer # Pop and operator of the operators stack and one or two operands of the # operand stack, and assembled into a call to the appropriate function. # The function call is pushed onto the operand stack def reduce( operators, operands ): op = operators.pop() right = operands.pop() if op == 'not': operands.append( "%s(%s)" % ( op.upper(), right )) else: left = operands.pop() operands.append( "%s(%s,%s)" % ( op.upper(), left, right )) prec = { '(' : 0, # operator precedence 'imp' : 1, 'or' : 2, 'nor' : 3, 'xor' : 3, 'equ' : 3, 'and' : 4, 'nand': 4, 'not' : 5 } # operand and operator stacks operands = [] operators = [] # Regular expression for parsing the infix expression. It has three # parenthesized groups, which are returned in a tuple by the groups() # method of a match object (mo). The tuple is unpacked into # corresponding variables in the for-statement. # # paren | logical operators (curop) |ident regex = r"([()])|(not|and|nand|or|nor|xor|equ|imp)|(\w+)" for paren,curop,ident in (mo.groups() for mo in finditer(regex,expr)): # identifiers (i.e., variable names) are pushed on the operand stack if ident is not None: operands.append( ident ) # left parens are pushed on the operator stack elif paren == '(': operators.append( paren ) # for a right paren, the stacks are reduced until the matching # left paren is encountered. The left paren is discarded. elif paren == ')': while operators[-1] != '(': reduce( operators, operands ) _ = operators.pop() else: # while the operator being parsed (curop) has a lower # precedence than the one on the top of the operator stack, # reduce the higher priority operator. Then push the curop # onto the opperator stack while operators != [] and prec[ curop ] <= prec[ operators[-1] ]: reduce( operators, operands ) operators.append( curop ) # after the input expression is exhausted, reduce the operands on the # operand stack until it is empty while operators != []: reduce( operators, operands ) return operands.pop() def NOT(a): return not a def AND(a,b): return a and b def NAND(a,b): return not AND(a,b) def XOR(a,b): return a ^ b def EQU(a,b): return not XOR(a,b) def OR(a,b): return a or b def NOR(a,b): return not OR(a,b) def IMP(a,b): return b if a else True stmnt = compile(toPrefix(expr),'<string>','eval') format = "%-5s %-5s %-5s" print format % (' A ',' B ',expr) for A in (True,False): for B in (True,False): print format % (A,B,eval(stmnt,locals())) if __name__ == "__main__": import doctest doctest.testmod(verbose=True)
Problem 48: Print truth table for logical infix expression having an arbitrary number of variables
def table(expr): ''' Print a truth table for infix boolean expression with arbitrary number of variables. Implemented as an interpreter using a recursive decent parsing technique. Uses the tokenize module to convert expression to tokens. >>> table('A and (B or C) equ A and B or A and C') A B C A and (B or C) equ A and B or A and C True True True True True True False True True False True True True False False True False True True True False True False True False False True True False False False True >>> table('(not A or B) equ (A imp C)') A B C (not A or B) equ (A imp C) True True True True True True False False True False True False True False False True False True True True False True False True False False True True False False False True ''' from tokenize import generate_tokens from StringIO import StringIO def evaluate(expr): '''entry point for recursive decent parser/interpreter''' readline = StringIO(expr).readline # tokenize returns a tuple. element[1] is the text of the token # tokenize returns '' as the final token tokens = [t[1] for t in generate_tokens(readline)][:-1] return imp_expr(tokens) def imp_expr(tokens): '''imp_expr := or_expr [ 'imp' or_expr ]''' value = or_expr(tokens) if tokens and tokens[0] == 'imp': _ = tokens.pop(0) right = or_expr(tokens) value = right if value else True return value def or_expr(tokens): '''or_expr := xor_expr [ ('or'|'nor') xor_expr ]''' value = xor_expr(tokens) if tokens and tokens[0] in ('or','nor'): op = tokens.pop(0) right = xor_expr(tokens) value = value or right if op == 'nor': value = not value return value def xor_expr(tokens): '''xor_expr := and_expr [ ('xor'|'equ') and_expr ]''' value = and_expr(tokens) if tokens and tokens[0] in ('xor','equ'): op = tokens.pop(0) right = and_expr(tokens) value = value == right if op == 'xor': value = not value return value def and_expr(tokens): '''and_expr := not_expr [ ('and'|'nand') not_expr ]''' value = not_expr(tokens) if tokens and tokens[0] in ('and','nand'): op = tokens.pop(0) right = not_expr(tokens) value = value and right if op == 'nand': value = not value return value def not_expr(tokens): '''not_expr := [ 'not' ] atom''' if tokens and tokens[0] == 'not': _ = tokens.pop(0) value = not atom(tokens) else: value = atom(tokens) return value def atom(tokens): '''atom := '(' imp_expr ')' | variable ''' if tokens and tokens[0] == '(': _ = tokens.pop(0) value = imp_expr(tokens) _ = tokens.pop(0) else: ident = tokens.pop(0) value = variable[ident] return value def combos(variable,varlist): '''generate all possible combinations of values for the variables in "varlist", updating the values in "variable" ''' if varlist == []: yield [] else: for variable[varlist[0]] in (True,False): for rest in combos(variable,varlist[1:]): yield [variable[varlist[0]]] + rest keywords = 'and nand xor equ or nor imp not ( )'.split() readline = StringIO(expr).readline # generate a list of variable names, by parsing 'expr' and collecting # text tokens that aren't keywords. The values are kept in the dict # 'variable'. variable = {} for token in generate_tokens(readline): text = token[1] if text != '' and text not in keywords: variable[text] = text varlist = sorted(variable.keys()) variable['result'] = expr # format has a '%(varname)-5s' field for each variable and result format = " ".join("%%(%s)-5s" % v for v in varlist + ["result"]) print format % variable for _ in combos(variable,varlist): variable["result"] = evaluate(expr) print format % variable if __name__ == "__main__": import doctest doctest.testmod(verbose=True)
Problem 49: Generate list of n-bit Gray codes.
def binaryString(n,w=0): """return binary representation of 'n' as a 'w'-width string. >>> binaryString(6) '110' >>> binaryString(3,4) '0011' """ from collections import deque bits = deque() while n > 0: bits.appendleft(('0','1')[n&1]) n >>= 1 while len(bits) < w: bits.appendleft('0') return ''.join(bits) def gray(width): """return list with 'width'-bit gray code. >>> gray(1) ['0', '1'] >>> gray(2) ['00', '01', '11', '10'] >>> gray(3) ['000', '001', '011', '010', '110', '111', '101', '100'] """ return [binaryString(n ^ (n>>1),width) for n in range(2**width)] if __name__ == "__main__": import doctest doctest.testmod(verbose=True)
Problem 50: Generate Huffman codes
def huffman(freqtable): """Return a dictionary mapping keys to huffman codes for a frequency table mapping keys to frequencies. >>> freqtable = dict(a=45, b=13, c=12, d=16, e=9, f=5) >>> sorted(huffman(freqtable).items()) [('a', '0'), ('b', '101'), ('c', '100'), ('d', '111'), ('e', '1101'), ('f', '1100')] """ from collections import defaultdict from heapq import heappush, heappop, heapify # mapping of letters to codes code = defaultdict(list) # Using a heap makes it easy to pull items with lowest frequency. # Items in the heap are tuples containing a list of letters and the # combined frequencies of the letters in the list. heap = [ ( freq, [ ltr ] ) for ltr,freq in freqtable.iteritems() ] heapify(heap) # Reduce the heap to a single item by combining the two items # with the lowest frequencies. while len(heap) > 1: freq0,letters0 = heappop(heap) for ltr in letters0: code[ltr].insert(0,'0') freq1,letters1 = heappop(heap) for ltr in letters1: code[ltr].insert(0,'1') heappush(heap, ( freq0+freq1, letters0+letters1)) for k,v in code.iteritems(): code[k] = ''.join(code[k]) return code if __name__ == "__main__": import doctest doctest.testmod(verbose=True)
Note: problems 51-99 still to be done (PLEASE edit this place-holder as you do more problems!)