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.
Related
I am trying to count the elements of a list of
lists.
I implemented the code in this way:
len1([],0).
len1([_X|Xs],N) :- len1(Xs,N1), N is N1+1.
clist([[],[]],0).
clist([Xs,Ys],N):- len1(Xs,N1),len1(Ys,N2),N is N1+N2.
i re-use count element (len1 predicates) in a list, and seems work.
Anyone can say me if is nice work, very bad or can do this but it s preferable other (without len1).
I dont think is good implementation, and otherwhise seems not generic.
Ad example this work only with list, that contain two list inside. If i want make generic? i think need to use _Xs, but i try to change my code and not working.
in particular i try to change this:
clist([Xs,Ys],N):- len1(Xs,N1),len1(Ys,N2),N is N1+N2.
in
clist([_Xs],N):- len1(_Xs,N1),N is N1.
and obviously don't work.
Well you can apply the same trick for your clist/2 predicate: instead of solving the problem for lists with two elements, you can consider two cases:
an empty list [], in which case the total number is of course zero; and
a non-empty list [H|T], where H is a list, and T is the list of remaining lists. In that case we first calculate the length of H, we the calculate (through recursion) the sum of the lists in T and then sum these together.
So we can implement this as:
clist([], 0).
clist([H|T], N) :-
length(H, HN),
clist(T, TN),
N is HN + TN.
The above can be improved by using an accumulator: we can define a predicate clist/3 that has a variable that stores the total number of elements in the list this far, in case we reach the end of the list, we unify the answer with that variable, like:
clist(L, N) :-
clist(L, 0, N).
clist([], N, N).
clist([H|T], N1, N) :-
length(H, HN),
N2 is N1 + HN,
clist(T, N2, N).
Yes, you were correct in wanting to generalize your definition. Instead of
clist([[],[]],0).
(well, first, it should be
clist( [] , 0).
Continuing...) and
clist([Xs,Ys], N):- len1(Xs,N1), len1(Ys,N2), N is N1+N2.
which handles two lists in a list, change it to
clist([Xs|YSs], N):- len1(Xs,N1), len1(YSs,N2), N is N1+N2.
to handle any number of lists in a list. But now the second len1 is misapplied. It receives a list of lists, not just a list as before. Faced with having to handle a list of lists (YSs) to be able to handle a list of lists ([Xs|YSs]), we're back where we started. Are we, really?
Not quite. We already have the predicate to handle the list of lists -- it's clist that we're defining! Wait, what? Do we have it defined yet? We haven't finished writing it down, yes, but we will; and when we've finished writing it down we will have it defined. Recursion is a leap of faith:
clist([Xs|YSs], N):- len1(Xs,N1), clist(YSs,N2), N is N1+N2.
Moreover, this second list of lists YSs is shorter than [Xs|YSs]. An that is the key.
And if the lists were arbitrarily deeply nested, the recursion would be
clist([XSs|YSs], N):- clist(XSs,N1), clist(YSs,N2), N is N1+N2.
with the appropriately mended base case(s).
Recursion is a leap of faith: assume we have the solution already, use it to handle smaller sub-cases of the problem at hand, simply combine the results - there you have it! The solution we assumed to have, coming into existence because we used it as if it existed already.
recursion( Whole, Solution ) :-
problem( Whole, Shell, NestedCases),
maplist( recursion, NestedCases, SolvedParts),
problem( Solution, Shell, SolvedParts).
A Russian matryoshka doll of problems all the way down, turned into solutions all the way back up from the deepest level. But the point is, we rely on recursion to handle the inner matryoshka, however many levels it may have nested inside her. We only take apart and reassemble the one -- the top-most.
howMany([],_,0).
howMany([Head|Tail],X,Times):-
\+(Head = X),
howMany(Tail,X,Times1),
Times is Times1.
howMany([Head|Tail],X,Times):-
Head = X,
howMany(Tail,X,Times1),
Times is Times1 +1.
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.
I can't understand the difference between accumulators and holes.
Formally:
Accumulators
*Use 2 arguments to organize the building of some
output structure
*One is for result so far
*One is for final result
Holes
*Use 2 arguments to organize the building of some
output structure
*One is for final result
*One for a hole in the final result where further information can be put
Is this flattener using a hole or an accumulator?
How do I convert it to use the other one?
my_flatten(In,[],Out).
my_flatten([],Acc,Acc).
my_flatten([H|T],Acc,Out) :-
my_flatten(T,Acc,TOut),
my_flatten(H,TOut,Out).
my_flatten(X,Acc,[X|Acc]) :-
X \= [],
X \= [_|_].
Accumulators are variables that are used in recursion to count or build a structure such as a conventional list. Holes are used in difference lists (Or other difference structures), where a list is represented as a pair of terms, the front and the back. Often the back would be a variable or a hole. It is usual to use an infix notation so a list would be:
Front-Back
So List = [a,b,c|Hole]-Hole for example.
Using difference lists allows you to have constant time appending by just rearranging variables.
In difference notation L-L is the empty list, [1|Z]-Z is the list containing 1, and [1,2,3|Z]-Z is the list containing 1,2 and 3. You can rectify a difference list by unifying it with Y-[].
Flatten in difference notation is given as worksheet 28 in 'Clause and effect - by W.F.Clocksin'
flatten(X,Y):-flatpair(X,Y-[]) %rectifying the list
flatpair([],L-L). %empty list
flatpair([H|T],L1-L3):-flatpair(H,L1-L2),flatpair(T,L2-L3).
flatpair(X,[X|Z]-Z).
Which is much more efficient then using accumulators and constructing partial lists or using append.
I have a list of substitutions between variables and values
they are presented in a list [[x,y],[1,2]] (meaning that the value of x equals 1, and the value of y equals 2).
I want to change the list to a list of pairs meaning [[x,1],[y,2]],
I tried to use append so that I will create a pair in each step of the recursion and append it to a new list but i have problem of doing so (mainly selecting the head and tails of each pair)
your example it's a bit contrived, but transpose it's a bit overkill, specially if you're going to implement yourself. I think this should solve your need
'change an order of list of lists'([X_Y_Z, A_B_C], XA_YB_ZC) :-
findall([K, V], (nth1(I, X_Y_Z, K), nth1(I, A_B_C, V)), XA_YB_ZC).
Chapter 8 of The RealWorldHaskell
globToRegex' (c:cs) = escape c ++ globToRegex' cs
This function is not tail recursive and it says that the answer relies on Haskell non-strict(lazy) evaluation strategy. The (++) operator's simple definition lies in the following and it's not tail recursive.
(++) :: [a] -> [a] -> [a]
(x:xs) ++ ys = x : (xs ++ ys)
[] ++ ys = ys
In a strict language, if we evaluate "foo" ++ "bar", the entire list is constructed, then returned. Non-strict evaluation defers much of the work until it is needed.
If we demand an element of the expression "foo" ++ "bar", the first pattern of the function's definition matches, and we return the expression x : (xs ++ ys). Because the (:) constructor is non-strict, the evaluation of xs ++ ys can be deferred: we generate more elements of the result at whatever rate they are demanded. When we generate more of the result, we will no longer be using x, so the garbage collector can reclaim it. Since we generate elements of the result on demand, and do not hold onto parts that we are done with, the compiler can evaluate our code in constant space.
(Emphasis added.)
The explanation above in bold is something essential to Haskell, But
How can we comprehend that?
What happend in the underlying?
"x:(xs ++ ys) will evalute in constant space", how? It sounds what tail recursion does!
Remember that "foo" is just syntactic sugar for 'f':'o':'o':[].
That is, String is just an alias for [Char] which is just a linked list of characters.
When client code is consuming the linked list, it decomposes it back into a head and tail (e.g. x:xs), does something with the head (if desired), and then recurses for the tail.
When your code is constructing the linked list, because of lazy evaluation, all it needs to do is return a thunk or promise that it will return a linked list when asked for. When the head is dereferenced, it is supplied on demand, and the tail is left as a promise for the rest of the list.
It should be easy to see that as long as the list is not copied or otherwise stored, each thunk will get used once and then discarded, so that the overall storage space is constant.
Many strict languages expose a mechanism (often called a generator) to accomplish the same kind of lazy list generation, but with lazy evaluation such features come "for free" as part of the language -- in essence, all Haskell lists are generators!
Relying on lazy evaluation rather than tail recursion is a characteristic of Haskell in comparison to other FP languages. The two play related roles in terms of limiting memory usage; which one is the appropriate mechanism depends on the data being produced.
If your output may be incrementally consumed, then you should prefer to take advantage of lazy evaluation, as output will only be generated as it is required, thus limiting heap consumption. If you eagerly construct the output, then you are resigning yourself to using heap, but can at least conserve stack by being tail recursive.
If your output can not be incrementally consumed -- perhaps you are computing an Int -- then lazyness can leave you with an unwanted pile of thunks whose evaluation will blow your stack. In this case, a strict accumulator and tail recursion are called for.
So, if you are eager you might waste heap building a big data structure. If you are lazy, you might defer simplification (e.g. reducing 1 + 1 to 2) to the heap, only to eventually sully your stack when paying the piper.
To play with both sides of the coin, ponder foldl' and foldr.
Tail recursion would keep the stack constant, but in a strict language the heap would grow as x : (xs ++ ys) was computed. In Haskell, because it is non-strict, x would be freed before the next value was computed (unless the caller held a reference to x unnecessarily), so the heap would also be constant.