All the below should be solved recursively, with no loops, unless otherwise noted.
----------------------------------
Integer part of logarithm.
Given b > 0 and an a positive real number x,
write a function that returns the integer part of log_b(x)
(_b denotes in base b). Obviously you cannot use log, round, floor,
or any other math function than comparisons and + - * /.
For example, log(8.0,3) should return 1, log(9,3) should return 2,
and log(9.5,3) should return 2 too.
Hint: Exploit the relation between the logs of x and x/b.
----------
Interleaving. Given two strings compute its interleaving.
The interleaving of "abc" and "xyz" is "axbycz". The interleaving
of "ab" and "xyz" is "axbyz". The interleaving of "abc" and "xy"
is "axbyc".
Write a function interleaving(x,y) that returns the interleaving
of x and y. Work until you have one version that runs in time
O(len(x)+len(y))
------------------------
Permutation. A permutation on a set I is bijective function from I to I.
If P is a permutation, P^i(x) denotes the result of applying P i times,
starting from x, so P(P(... P(P(x)))...)), where P appears P times.
P^0(x) is of course x itself.
When I is the interval [0..n-1], we can present P as the list
[P(0),P(1),...,P(n-1)].
1. Write a function iterate(P,x,i) that given permutation P presented
this way, index i, and value x, returns P^i(x). For example,
for P=[3,5,2,4,0,1], i = 3, and x = 4, it should return
P(P(P(4))) = P(P(0)) = P(3) = 4.
It should run in time O(i).
2. A permutation is a cycle if, starting from any x, the only
way to get back to x applying P repeatedly is going through
all the elements of the permutation. For example, the permutation
above is not a cycle because we go from 4 to 4 in only 3 steps,
and in fact from 2 to 2 in 1 step. Write a function is_cycle(P)
that tells whether or not P is a cycle.
Hint. It is difficult to program is_cycle recursively directly.
Use an auxiliary recursive function that given x says how many steps you
need in P to get back to x, then use it to give is_cycle.
It should run in time O(len(P)).
------------------------
Replacement in strings.
Let c1, c2, c3 be characters.
We say that "we replace c1 with c2 conditionally to c3"
in a string s if every occurrence of c1 in s is replaced
with a c2, provided it is followed by a c3.
Write a function creplace(s,c1,c2,c3) that returns
the result of replacing c1 with c2 conditionally to c3 in s.
For example, creplace("rabadan","a","x","d") is "rabxdan".
creplace("rabadaba","a","x","b") is "rxbadaxa",
and creplace("aaa","a","x","a") is "xxa".
The function cannot contain any loops and use no
method of string other than accessing positions and sublists
(lst[....], lst[...:...]), and len(.).
-------------------
Computing integer division and mod from basics.
Suppose that you are given a faulty python interpreter
where the integer division // , modulus % and divmod
do not work.
You want to write your own function divmod(a,b)
that returns the tuple (x,y) where x == a//b and y == a % b.
1. write a version using a loop that repeatedly subtracts b from a
2. inspired from that, write a recursion version that does the same.
3. Now let us do it faster. Observe that if a is even, then
a // b == 2* ((a//2) // b)
and give a recursive version using this fact.
Be careful with the base cases.
You cannot use floats and operations on floats, and stuff such
as logarithms and exponentials. Integers only.
But divisions and modulus by 2, x//2, and x%2 are allowed;
the python compiler can implement these correctly.
-------------------------------------------
-------------------------------------------
Solutions:
*** pay attention to the reasining that leads to the solution
*** even more than to the coding in Python
----------------------
Integer part of logarithm.
If you recall the definition of log, we have y = log_b(x)
if b^y = x.
Therefore, if y = log_b(x), then we have y-1 = log_b(x/b).
Also, log(b,b) = 1, so for x < b, the integer part of log(x,b) is 0.
def log(x,b):
if x < b:
return 0
else:
return 1 + log(x/b,b)
----------------------
Interleaving. To get the recursion, observe that
the interleaving of x and y is
- y if x is empty
- x if y is empty
- otherwise, as both x and y are non-empty, they both have a first
character. Then the interleaving of x and y is the first character
of x, followed by the first character of y, followed by
the interleaving of the rest of x and y after removing their
first characters.
def interleaving(x,y):
if x == "":
return y
elif y == "":
return x
else:
s = x[0]+y[0]
s += interleaving(x[1:],y[1:])
return s
Analysis exercise:
Assume s1 += s2 runs in linear time in len(s1)+len(s2) (as it seems to be),
and recall that extracting s[1:] take linear time in len(s).
Given this, argue that for strings x and y of equal length n,
interleaving(x,y) runs in time O(n^2).
Improvement exercise:
Write a version that runs in linear time, O(len(x)+len(y)),
by adding an index that avoids the copying
--------
Permutations:
iterate(P,x,i):
let us try to induct on i, the number of steps
if i==0, then it's easy: the place we go from x by applying P 0 times is x
else if i>0, we take a first step and go to P(i). Then we need to take i-1 steps,
but starting at P(i), and that is the result
def iterate(P,x,i):
if i == 0:
return x
else:
return iterate(P,P[x],i-1)
It is possible to do it another way: Here we first take 1 step,
then let the recursion do the next i-1 steps.
But we can take the first i-1 steps recursively, see where we land,
then take the extra step. The following is an equally good solution:
def iterate(P,x,i):
if i == 0:
return x
else:
y = iterate(P,x,i-1)
return P[y]
For is_cycle, one needs to realize that if
the walk starting at *some* x takes you through all the elements
of P, then it doesn't matter which x you start with. That is,
this is true for some x if and only if it is true for all x
So: For simplicity, let us start at 0. We know
that P is a cycle if, starting and 0 and applying P repeatedly,
we get back to 0 only after having visited len(P) elements.
Assuming P is a permutation, we know for sure that we get back to 0
in at most len(P) steps.
def is_cycle(P):
return time_to_reach_zero(P,0) == len(P)
time_to_reach(P,x) tells how many applications of P we need
to get to 0 starting from x. Well, that is 0 if we are in 0
and, otherwise, 1 more than we need to get from P(x) to 0.
def time_to_reach_zero(P,x):
if x == 0:
return 0
else:
return 1 + time_to_reach_zero(P,P[x])
There is a potential danger in this approach: what if
we enter an infinite loop: Suppose that P takes us this way:
0 --> 4 --> 7 --> 5 --> 7 --> 5 --> 7 --> 5 --> 7 --> ...
and so on and so on, so that it never ends.
Think about it before looking at the next lines.
THINK THINK THINK BEFORE PROCEEDING DOWNWARD
The above cannot happen if P is really a permutation,
because we must have P(4)=7 and P(5)=7, so P is not bijective.
Change time_to_reach_zero and is cycle so that is_cycle returns
False when P is not a permutation as well (being a permutation
is a necesary condition to be a cycle).
------------
replacement in strings.
Something similar to this should work:
def creplace(s,c1,c2,c3):
if len(s) < 2:
return s
elif s[0] == c1 and s[1] == c3:
return c2.append(creplace(s[1:],c1,c2,c3)
else
return c1.append(creplace(s[1:],c2,c2,c3)
----------------
Computing integer division and mod from basics.
First version with loop:
def divmod(a,b):
c = a
x = 0
while c >= b:
x = x+1
c = c-b
return (x,a-b*c)
Recursive version, basically mimicking the loop:
def divmod(a,b)
if (a < b):
return (0,a)
else:
(x,y) = divmod(a-b,b)
return (x+1,y)
Now with the shortcut of dividing by 2:
def divmod(a,b)
if (a < b):
return (0,a)
elif a % 2 == 1:
(x,y) = divmod(a-b,b)
else:
(x,y) = divmod(a//2,b)
return (2*x,a - (2*x)*b)