Fastest way to join 2 lists? - scheme

I'm making a program and in a very specific part I need to join 2 lists of objects. I've always used append to do such things but I've read that append it's not that fast. These two lists I need to join will eventually grow. So I'm looking for the fastest way for the computer to do it.
Should I use
(append list1 list2)
Or better:
(foldr cons list1 list2)
I do not think I mind about the order of the elements.

This should be slightly faster for lists of equal length:
(foldl cons list1 list2)
Why? because it starts consing elements from list2 at the head of list1, whereas append will first cons all of the elements of list1 before consing list2 at the end - and notice that I'm using foldl, not foldr. That'll have the benefit of running in constant space (because of tail-recursion).
To put it another way: the foldl solution runs in constant space and is O(length-of-list2) whereas the append solution is O(length-of-list1) and doesn't run in constant space, bear that in mind if the lists have different lengths.

Related

Sort procedure in scheme

How can i write my own Sort procedure in scheme which takes in a procedure and sorts it based on the procedure!
If we can,What is the procedure?
example- (sort '(2 4 9 5 3) >) yields (9 5 4 3 2)
And can anyone suggest a procedure for searching each element of one list in a second list!
It's pretty straightforward. You just give the variable to hold the predicate a name and implement the sort strategy of you liking..
;; implements a 2 element sort
(define (my-sort2 lst <)
(let ((fst (car lst)) (snd (cadr lst)))
(if (< snd fst)
(list snd fst)
lst ))) ; already in correct order
(sort '(1 2) >) ; ==> (2 1)
(sort '(1 2) <) ; ==> (1 2)
In a slightly more advanced sorting algorithm you need not know both (< snd fst) and (< fst scd) so that in the case both are these are false you have the third option that they are the same.
Now run along to find the sorting algorithm that you'd like to implement. For few elements even professional libraries use Insertion sort and often for the larger data sets merge sort or quick sort are good choices.
Many of the sorting algorithms are faster if they are done in fixed sized vectors for longer lists. Thus most libraries actually first makes a mutable vector with the values, does the sort in place, then turns it back into a list. In Scheme implementations like DrRacket you can right click their sort and open the defining file to see how it's done. Needless to say its quite advanced in order to do well in most cases.

MicroKanren - what are the terms?

Having a little trouble understanding the core terms of the MicroKanren DSL. Section 4 says:
Terms of the language are defined by the unify operator. Here, terms of the language consist of variables, objects deemed identical under eqv?, and pairs of the foregoing.
But they never describe what the "pairs" actually mean. Are the pairs supposed to represent equality of two subterms, like so:
type 'a ukanren = KVar of int | KVal of 'a | KEq of 'a kanren * 'a kanren
So a term like:
(call/fresh (λ (a) (≡ a 7)))
Generates a pair for (≡ a 7)?
Edit: upon further thought, I don't think this is it. The mentions of "pair" in the paper seem to come much later, with extensions and refinements to the basic system, which would mean the pairs have no meaning in the terms for the basic intro. Is this correct?
In this context, "pair" just means a cons pair, such as (5 . 6) or (foo . #t). An example unification between two pairs is:
(call/fresh
(λ (a)
(call/fresh
(λ (b)
(≡ (cons a b) (cons 5 6))))))
which associates a with 5 and b with 6.
Sorry for the confusion and difficulty!! And thank you for the question!
You can think of the (typical) Kanren term language as having a single binary functor tag cons/2, and an infinite quantity of constants (the exact makeup changes from embedding to embedding).
Under the assumption that cons/2 is the only (n < 0)-ary functor tag, every compound term will have been built with it. If you look up standard presentations of unification (e.g. Martelli-Montanari) you'll usually see a step f(t0,...,tn) g(s0,...,sm) => fail if f =/= g or n =/= m. We handle clash-failure in unification when comparing ground atomic terms; for us, compound terms must necessarily be of the same arity and tag.
pair? recognizes conses. In Racket, in fact, they provide a cons? alias, to make this clearer!
Once again, thank you!

Is the runtime of the append procedure O(n)?

For example, in OCaml when you are appending an item to a list of length n.
x#[mylist]
Yes, the runtime of # in OCaml is O(n) (where n is the length of the left operand).
Generally appending to the end of an immutable singly linked list (or an immutable doubly linked list for that matter) will always be O(n).
Your code snippet doesn't match your question, which suggests you're confused about what the operator does or which operator to use.
The # or List.append operator concatenates 2 lists, and list1 # list2 takes O(length(list1)) time and is not tail-recursive. rev_append is tail-recursive but still O(n) in time. The usual way to add an item to a list however is with the :: constructor, and item :: mylist takes O(1) time.
Yes, as mentioned, there are two reasons why it must be O(n):
You must iterate to the end of the singly-linked list, which takes O(n)
Since pairs are immutable, you must copy all the pairs in the first list in order to append, which also takes O(n)
A related interesting topic is tail-recursive vs non-tail recursive ways to append
In summary, yes.
To illustrate, a simple (not tail-recursive) append function could be written as follows:
let rec (#) xs ys =
match xs with
| [] -> ys
| x::xs' -> x::(xs' # ys)
So internally append (#) breaks down the first list (xs) and uses cons (::) operator to build the resulting list. It's easy to see that there are n steps of prepending (::), where n is the length of the first list.

DrRacket - Intermediate Student With Lambda - List ordering

I have an assignment question that asks me to write a function, using lambda, that consumes a list of numbers and removes all but the first occurrence of each number. My function is I think quite nice, with the exception that it produces the wrong result! It produces a list that contains the last occurrence of each number. It produces a list with the same values as the proper list, just in different order. Here's my code:
(define (remove-duplicates numlist)
(foldr (lambda (a b)
(cond
[(not (member? a b)) (cons a b)]
[else b])) empty numlist))
I've tried using foldl instead of foldr but, not surprisingly, it produces the correct list, but in reverse. Is there a way of producing the correct list without resorting to reversing the list created by foldl with another lambda expression?
Please keep in mind, this is homework so no explicit answers please.
Thanks everyone!
Think about how foldr behaves, semantically. It starts at the rightmost element of the list, and performs the fold, moving leftwards. That means that in your lambda function, a represents the element directly to the left of the thus-far-folded list, and b represents the result of folding everything to the right of the list element a. With this in mind, consider:
[(not (member? a b)) (cons a b)]
[else b]
You check whether the list b already contains a. If it does, then you discard a, keeping b as it is. This explains why your code keeps the last (rightmost) occurrence, and not the first. If you wish to perform a right fold, then, you must change your logic. You need to somehow "purge" b of the offending element a that it contains.
[(not (member? a b)) (cons a b)]
[else (cons a (purge a b))]
This way, you will eventually keep only the leftmost, rather than the rightmost, occurrence of each unique element. It may be wise to perform member? and purge simultaneously, since they both must traverse the list; this would require additional refactoring.
Note that, since the function takes O(n2) time anyways, it really doesn't hurt the time complexity to add a O(n) reverse to the foldl version.

I need to join two lists, sort them and remove duplicates. Is there a better way to do this?

I have two unsorted lists and I need to produce another list which is sorted and where all the elements are unique.
The elements can occur multiple times in both lists and they are originally unsorted.
My function looks like this:
(defun merge-lists (list-a list-b sort-fn)
"Merges two lists of (x, y) coordinates sorting them and removing dupes"
(let ((prev nil))
(remove-if
(lambda (point)
(let ((ret-val (equal point prev)))
(setf prev point)
ret-val))
(sort
(merge 'list list-a list-b sort-fn) ;'
sort-fn))))
Is there a better way to achieve the same?
Sample call:
[CL]> (merge-lists '(9 8 4 8 9 7 2) '(1 7 3 9 2 6) #'>)
==> (9 8 7 6 4 3 2 1)
Our neighbourhood friendly Lisp guru pointed out the remove-duplicates function.
He also provided the following snippet:
(defun merge-lists (list-a list-b sort-fn test-fn)
(sort (remove-duplicates (append list-a list-b) :test test-fn) sort-fn))
I think I would first sort the two lists separately and then merge them with a function that also skips over duplicates. This should be a bit faster as it requires one less traversal of both lists.
P.S.: I doubt it can be done much faster as you basically always need at least one sort and one merge. Perhaps you can combine both in one function, but I wouldn't be surprised if that doesn't make a (big) difference.
If the lists are sorted before you merge them, they can be merged, duplicate-removed and sorted at the same time. If they are sorted AND duplicate-free, then the merge/sort/duplicate-remove function becomes really trivial.
In fact, it might be better to change your insert function so that it performs a sorted insertion that checks for duplicates. Then you always have sorted lists that are free of duplicates, and merging them is a trivial matter.
Then again, you might prefer to have a fast insert function at the cost of sorting/removing duplicates later on.
Wouldn't the remove-duplicates function operate better if the sort was applied before the remove-duplicates?
As Antti pointed out, you probably want to leverage REMOVE-DUPLICATES and SORT, though I'd probably use a keyword (or optional argument) for the test function:
(defun merge-lists (list-1 list-2 sort-fn &key (test #'eql))
...)
or
(defun merge-lists (list-1 list-2 sort-fn &optional (test #'eql)
...)
This way, you won't have to specify the test function (used by REMOVE-DUPLICATES to test for "is these considered duplicates"), unless EQL is not good enough.
Sounds like you need to be using Sets.

Resources