Let's say my reverse function in Scheme is coded in the following way:
(define (reversee lst)
(if (null? lst)
'()
(append (reversee (cdr lst)) (list (car lst)))))
Why is the performance of this procedure O(n^2)?
Well, consider what the function has to do:
for a zero-element list it just returns the list.
for a 1 element list it appends the reverse of a zero element list and the first element.
for a 2 element list it appends the reverse of a 1 element list and the first element
... and so on.
It's obvious from this that to reverse an n-element list it calls itself recursively n times. So this looks like the time complexity is O(n), right?
Not so fast: for each recursive call it appends two lists. So we need to worry about the complexity of append. Well, let's write the best append we can:
(define (my-append l1 l2)
(if (null? l1)
l2
(cons (car l1) (my-append (cdr l1) l2))))
OK, so what's the complexity of this? First of all, only depends on the length of l1, so we only need to worry about that:
if l1 is a zero-element list it returns l2
if l2 is a 1-element list is conses its first element onto the result of appending the rest of it to l2
... and so on
So to append an n-element list to some other list there are n steps. So what is the time complexity of each of these steps? Well, it's constant: cons and cdr and car take constant time. So the time complexity of append is the length of the first list.
So this means that the time complexity of your reverse function is
n + n-1 + n-2 + ... + 1
It's a famous result that the value of such a sum is n(n+1)/2 (you do get this result by writing it as ((n + 1) + (n - 1 + 2) + ... (1 + n))/2 = ((n + 1) + ... (n + 1))/2 with n terms in the sum. So the complexity is n(n+1), which is asymptotically equal to n^2.
There is a better version of reverse, of course.
A more readable and understandable O(N) reverse....
(define (reverse lst)
(let reverser ((lst lst)
(reversed '()))
(if (null? lst)
reversed
(reverser (cdr lst) (cons (car lst) reversed)))))
(reverse '(1 2 3))
By building up the reversed list as it goes with a tail-recursive approach, it only has to traverse the list once.
Related
I am trying to figure out how to access the elements in a nested list in LISP. For example:
((3 (1 7) (((5)))) 4)
If I use dolist, i run into the brackets. Is there any method to just get the elements from the list?
This is actually a surprisingly subtle question! It's in some ways the equivalent of asking: how do I get the nested elements of an HTML DOM, by specifying a pattern. (more on that aspect later)
If you just want to get the non-list elements as a sequence, e.g.
((3 (1 7) (((5)))) 4) -->
(nth 3 (3 1 7 5 4)) -->
5
You can use the 'cheat' way: the flatten function in the CL alexandria library. (via quicklisp)
(ql:quicklisp :alexandria)
(nth 3 (alexandria:flatten '((3 (1 7) (((5)))) 4)))
Which gives us the sought after,
5
But, the alexandrian function is simple enough that we can take a look at the source code itself:
(defun flatten (list)
(cond
((null list)
nil)
((consp (car list))
(nconc (flatten (car list)) (flatten (cdr list))))
((null (cdr list))
(cons (car list) nil))
(t
(cons (car list) (flatten (cdr list))))))
As you can see, it's a recursive function -- at each level of recursion it asks the question: what is the object that I'm flattening? If it's the empty list, return nil. I'm done!
Otherwise it has to be a non empty list. If the first element of the list is also a list then flatten that and also flatten the cdr of the function argument list and concatenate the results.
If the first element is not a list and the second element is '(), that must mean we have a list with one element: return it.
The final case case, which exhausts our possibilities is that the first element in the list is an atom while the rest of the list is a list with at least one element. In that case concatenate the first element with the results of a flatten performed on the rest of the list.
The fact that the description in English is so ponderous shows the power of recursion, (and also my own lack of fluency when describing it).
But there's actually another way your question could interpreted: if I have a list that looks something like: ((n1 (n2 n3) (((n4)))) n5) How do I get at n2, even if n2 is itself a list? Our previous recursive algorithm won't work -- it depends on n2 not being a list to know when to stop. But, we can still use recursion and the very list we're searching as the basis for a pattern:
;; For each element in our pattern list look for a corresponding
;; element in the target, recursing on lists and skipping atoms that
;; don't match.
(defun extract-from-list (pattern target to-find)
(dotimes (i (length pattern))
(let ((ith-pattern (nth i pattern)))
(cond
((consp ith-pattern)
(let ((results
(extract-from-list (nth i pattern)
(nth i target)
to-find)))
(when results
(return results))))
(t
(if (eq to-find ith-pattern)
(return (nth i target))))))))
Note that,
(extract-from-list
'((n1 (n2 n3) (((n4)))) n5) ;; The pattern describing the list.
'((3 (1 7) (((5)))) 4) ;; The list itself.
'n4) ;; which of the elements we want.
still returns the old answer:
5
But,
(extract-from-list
'((n1 (n2 n3) (n4)) n5) ;; The pattern describing the list, note (n4) rather than (((n4)))
'((3 (1 7) (((5)))) 4) ;; The list itself.
'n4) ;; The element we want to pull from the list
Returns
((5))
Magic! One of the aspects of Lisp that makes it so extraordinarily powerful.
In the following code:
(define l (cons 1 2))
l
(list? l)
(pair? l)
(list-ref l 0) ; works;
(list-ref l 1) ; DOES NOT WORK;
Output:
'(1 . 2)
#f
#t
1
Error: list-ref: index reaches a non-pair
index: 1
in: '(1 . 2)
Why (list-ref l 0) works if l is not a list. Otherwise, why (list-ref l 1) does not work. This behavior seems to be inconsistent and confusion creating.
The functions car and cdr also work, returning 1 and 2, respectively. Why should cdr work if this is not a list. One would expect car to return 1.2 and not just 1.
From the documentation:
The lst argument need not actually be a list; lst must merely start with a chain of at least (add1 pos) pairs.
This means the lst argument is allowed to be an improper list as long as the index is smaller than the index of the first pair whose cdr is a non-pair.
A list is simply a lot of pairs linked together terminating in an empty list. For example, (list 1 2 3) is equivalent to (cons 1 (cons 2 (cons 3 '()))).
There's no reason to loop through the entire list to check if l is a list, so as long as it looks like a list, scheme doesn't care if it terminates in an empty list. list-ref is basically defined as so:
(define (list-ref lyst index)
(cond ((= index 0) (car index))
(else (list-ref (cdr lyst) (- index 1)))))
So of course it will encounter an error when it tries to run (car 2)
As for the other question, car and cdr simply return the first element in a pair and the second element in a pair respectively.
I'm trying to write a function using Scheme that :
take a list of integers with more than two elements as a parameter
sum the n-th-element and (n+1)-th-element
return this list
Result should be as follows :
> (SumNeighbors (list 1 2 3 4))
(3 5 7)
I think I get the way to add elements but my recursion is totally wrong...
(define (SumNeighbors lst)
(if (not (null? (cdr lst)))
(append (list (+ (car lst) (car (cdr lst)))) (SumNeighbors (cdr lst)))))
Any help would be appreciated.
The solution to this problem follows a well-known pattern. I'll give you some hints, it'll be more fun if you find the answer by your own means:
(define (SumNeighbors lst)
(if <???> ; if there's only one element left
<???> ; we're done, return the empty list
(cons ; otherwise call `cons`
(+ <???> <???>) ; add first and second elements
(SumNeighbors <???>)))) ; and advance recursion
Notice the following:
Your solution is lacking the base case - what happens when the list we're traversing only has one element left? it's time to finish the recursion! and because we're building a list as the output, what should be the value returned?
We normally use cons to build an output list, not append. That's the natural way to build a list
The part of this procedure that falls outside the solution template is the fact that we stop when there's a single elment left in the list, not when the list is empty (as is the usual case)
You'll see that many procedures that iterate over an input list and return a list as output follow the same solution template, it's very important that you learn how and why this works, it's the foundation for writing solutions to other similar problems.
#!r6rs
(import (except (rnrs base) map)
(only (srfi :1) map))
(define (sum-neighbors lst)
(map + lst (cdr lst)))
The higher order function map as defined in SRFI-1 supports uneven lenght arguments. It will stop at the shortest list.
If you call (sum-neighbors '(1 2 3 4)) it will become (map + (1 2 3 4) (2 3 4)) which is the same as (cons (+ 1 2) (cons (+ 2 3) (cons (+ 3 4) '())))
I'm really puzzled on how to do this... I can't even figure out how to start, I know how to do it for a binary tree but I want to be able to do it with any form of nested list, can anyone help me out please?
For this one, you need to use the template for traversing an arbitrarily nested list of elements. For example, study this procedure that copies an arbitrarily nested list:
(define (copy lst)
(cond ((null? lst) ; if list is empty
'()) ; return the empty list
((not (list? (car lst))) ; if current element is not a list
(cons (car lst) ; cons current element
(copy (cdr lst)))) ; with the rest of the list
(else ; if current element is a list
(cons (copy (car lst)) ; cons recursive call over current element
(copy (cdr lst)))))) ; with recursive call over rest of the list
A little convention first. Let's say that 1 is the base level, and that all the elements returned will be in a flat output list (without preserving the original structure of the input list). For example:
(elements-level '(1 2 3) 1)
; => '(1 2 3)
(elements-level '(1 (2) 3) 2)
; => '(2)
With the above template in mind, let's see how we can modify it for solving the problem at hand. I'll let you fill-in the blanks, because the question looks like homework:
(define (elements-level lst lvl)
(cond ((or (null? lst) (< lvl 1)) ; if list is empty or below level
'()) ; return the empty list
((not (list? (car lst))) ; if current element is not a list
(if (= lvl <???>) ; if `lvl` is the base level
(cons <???> ; cons current element in list
(elements-level <???> lvl)) ; and advance recursion over cdr part
(elements-level <???> lvl))) ; else advance recursion over cdr part
(else ; if current element is a list
(append ; use `append` for flattening the list
(elements-level <???> <???>) ; recur over car, decrease one level
(elements-level <???> <???>))))) ; recur over cdr, keep the same level
Test the procedure with this list, it must return '(1) for level 1, '(2) for level 2, and so on:
(elements-level '(1 (2 (3 (4 (5))))) 1)
; => '(1)
I think you can use recursion with steps as below:
Define a list to hold all elements at nth depth.
create a recursion function, which takes nested list, new list and n as argument
For each element of the nested loop, call the recursion function itself by passing the child list and depth as n-1.
Add all elements of the nested list to new list when n = 0.
Once this method is done, you will all elements of depth n in the new list.
Possible Enhancement:
If it is possible the some of the list elements don't extend up to nth level, then you may want to check the type of elements before calling the recursion method. If it's of type leaf, then simply add the element in the new list.
Is there a Common Lisp function that will swap two elements in a list given their indices and return the modified list?
You can use rotatef:
(rotatef (nth i lst) (nth j lst))
Of course, list indexing can be expensive (costing O(size of list)), so if you do this with any regularity, you'd rather want to use an array:
(rotatef (aref arr i) (aref arr j))
I would avoid indexing into the list twice by using nthcdr to get the cdr of the cons cell containing the first element that you want to swap and then use elt to get the remaining element out of the sublist. This means that you only have to actually index starting from the head of the list once.
(let ((list-tail (nthcdr i list)))
(rotatef (car list-tail)
(elt list-tail (- j i)))
list)
At least from my perspective, this is sufficiently tedious to justify a function.