Sequence operations
In the following exercises, you can treat sequences as lists or tuples, unless explicitly stated otherwise.
-
Define a function
mysum(seq)that returns the sum of all numbers in the listseq.def mysum(seq): """Return the sum of all numbers in seq. >>> mysum([1, 2, 3]) 6 >>> mysum([-1, 1, -1, 1]) 0 >>> mysum([]) 0 >>> mysum([0.5, 1.5, 2.5]) 4.5 """Solutions
With
while:def mysum(seq): total = 0 while seq: total += seq[0] seq = seq[1:] return totalWith
fordef mysum(seq): total = 0 for x in seq: total += x return totalWith
proc:from funcutils import proc mysum = lambda seq: proc((0,seq), lambda s: s[1], lambda s: (s[0]+s[1][0], s[1][1:]))[0]or
mysum = lambda seq: proc([0]+seq, lambda s: len(s)>1, lambda s: [s[0]+s[1]]+s[2:])[0]With
funcutils.reduce, which is I think as short as you get:from functools import reduce from operator import add mysum = lambda seq: reduce(add, seq)Another thing that the
reducesolution is superior to the others, including Python’s built-insum, is that it can project the generality of addition. You cannot find the “sum” of a list of lists withsum, but withmysumdefined withreduceyou can do it:>>> mysum([[1,2],[3],[4,6,7]]) [1, 2, 3, 4, 6, 7]One drawback is that
reduce-based solution cannot handle empty sequences. For that, you need to specify 0 as a third argument for arithmetic addition and[]for list concatenation. -
Define a function
myinsert(seq,index,item)that insertsitemat positionindexof the listseqand returns the new list.def myinsert(seq, index, item): """Insert item at index of seq. >>> myinsert([1, 2, 3], 1, 4) [1, 4, 2, 3] >>> myinsert([1, 2, 3], 5, 4) [1, 2, 3] >>> myinsert([True, False], 2, None) [True, False, None] >>> myinsert([],0,1) [1] >>> myinsert([1,4],1,[2,3]) [1, [2, 3], 4] """ - Define a function
myreversethat reverses a given list. Write 3 versions: usingwhile,for, andprocfrom funcutils. -
Define a function
myaggregate(seq, func, initial)that aggregates the elements of the listsequsing the binary functionfunc, starting with the valueinitial.def myaggregate(seq, func, initial): """Aggregate elements of seq using func starting with initial. >>> myaggregate([1, 2, 3], lambda x, y: x + y, 0) 6 >>> myaggregate([1, 2, 3, 4], lambda x, y: x * y, 1) 24 >>> myaggregate([], lambda x, y: x + y, 10) 10 >>> myaggregate([2, 3, 4], lambda x, y: x - y, 20) 11 """ -
Define a function
takewhile(alive, seq)that takes elements from the front of the sequenceseqas long as the predicate functionalivereturnsTrue. The function should return a new list containing the taken elements.def takewhile(alive, seq): """Take elements from seq while alive returns True. >>> takewhile(lambda x: x < 3, [1, 2, 3, 4, 1]) [1, 2] >>> takewhile(lambda x: x != 0, [1, 2, 3]) [1, 2, 3] >>> takewhile(lambda x: x > 0, [-1, 2, 3]) [] >>> takewhile(lambda x: True, [1, 2, 3]) [1, 2, 3] """Solutions
With
proc:def takewhile(alive, seq): return proc(([],seq), lambda s: s[1] and alive(s[1][0]), lambda s: (s[0] + [s[1][0]], s[1][1:]))[0] - Define a function
myfilter(test, seq)that removes items inseqthat returnFalsefortest.def myfilter(test, seq): """Filter seq by test. >>> myfilter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]) [2, 4, 6] """Notes
- Solve it with
whileand also withproc_seqorprocfrom funcutils. - The sequence can be implemented as a
tupleor alist. - If you use
tuples, you can only manipulate them using slicing and concatenation (+). - Lists allow for in-place manipulation.
- You can try versions with in-place manipulation (for list based solutions), but make sure you can solve the problem wihhout in-place manipulation.
- Solve it with
-
Define a function
myzip(seq1, seq2)that takes two sequencesseq1andseq2and returns a list of tuples, where each tuple contains one element fromseq1and the corresponding element fromseq2. The length of the returned list should be equal to the length of the shorter input sequence.def myzip(seq1, seq2): """Zip seq1 and seq2 into a list of tuples. >>> myzip([1, 2, 3], ['a', 'b', 'c']) [(1, 'a'), (2, 'b'), (3, 'c')] >>> myzip([1, 2], ['a', 'b', 'c']) [(1, 'a'), (2, 'b')] >>> myzip([], [1, 2, 3]) [] >>> myzip([1, 2, 3], []) [] """ -
Define a function
shuffle(seq)that returns a randomly shuffled version of the input sequenceseqwithout modifying the original sequence. Use therandommodule.import random def shuffle(seq): """Return a randomly shuffled version of seq. >>> shuffle([1, 2, 3]) [2, 1, 3] # example output; actual output will vary >>> shuffle(['a', 'b', 'c', 'd']) ['c', 'a', 'd', 'b'] # example output; actual output will vary """Notes and criteria
- You cannot use any built-in randomization functions that directly shuffle or sample from sequences.
- You are allowed to use the
randommodule to generate random numbers. - All auxiliary functions, e.g. for removing an element from a sequence, must be implemented by yourself.
- Solve it with
whileand withprocfrom funcutils.
Solutions
With
proc:def shuffle(seq): """Non-mutating shuffle of seq""" from funcutils import proc from random import randint return proc(([], seq), lambda s:s[1], lambda s: (lambda r: (s[0]+[s[1][r]], s[1][0:r]+s[1][r+1:])) (randint(0, len(s[1])-1)))[0]The above code is cryptic on purpose. If you can work out how my update function works, you are getting well adapted to functional programming. With a separate
remove_atfunction like:remove_at = lambda seq, index: seq[0:index] + seq[index+1:]the update function becomes a little clearer:
lambda s: (lambda r: (s[0]+[s[1][r]], remove_at(s[1], r))) (randint(0, len(s[1])-1))But still the
lambda-in-lambdamay be perplexing. We will deal with this kind of constructs later. You can simply usedef:def update(s): r = randint(0, len(s[1])-1) return (s[0]+[s[1][r]], remove_at(s[1], r))