Usually you do l[0] and l[1:] for car and cdr (first and rest) of the list.
The biggest challenge of this task is the forbidding of slicing.
But with
first, *rest = your_list
It works! Not only without slicing but also without indexing.
def first(l): # traditionally in Lisp languages `CAR`
car, *cdr = l
return car
def rest(l): # traditionally in Lisp languages `CDR`
car, *cdr = l
return cdr
def comp(lol1, lol2):
if len(lol1) == len(lol2) == 0: # recursion end condition
return True
# if first elements are lists, `comp`are the firsts and the rests
elif type(first(lol1)) == type(first(lol2)) == list:
return comp(first(lol1), first(lol2)) and \
comp(rest(lol1), rest(lol2))
# if first elements are atoms (non-lists), `==` the firsts and `comp`are the rest
else: # then the firsts are atoms!
return first(lol1) == first(lol1) and \
comp(rest(lol1), rest(lol2))
# traditionally in Lisp languages, you test not for list
# but for `atom` (whether the first elements of the lists are
# non-lists -> atomar). But `atom` is not that easy test in Python.
# so it is must more easy to ask whether both first elements are lists - and
# if not - then it is clear that the first elements of non-empty lists must be non-lists => atoms.
This works with Python3 but not with Python2.
For Python2 and Python3, you can use the function definitions:
def first(l):
return (lambda x, *y: x)(*l)
def rest(l):
return (lambda x, *y: y)(*l)
With single indexing and .pop()
Perhaps what your teacher thought of was:
def first(l):
return l[0]
def rest(l):
if l != []:
l.pop()
return l
else:
return []
# For definition of the `comp()` function see above
But this solution is problematic, because it changes
the input list, since Python does call-by-reference and not call-by-value. To avoid this, one has to deep-copy the list first. One can shallow-copy a list with slicing, but slicing is not allowed ...
Like:
q = [1, 2, [3, 4], [5, 6, 7], 8]
comp(q, q)
## True
# so far so good, but:
q
## []