I see this code on page 388 of the Odersky book on Scala:
class SlowAppendQueue[T](elems: List[T]) {
def head = elems.head
def tail = new SowAppendQueue(elems.tail)
def enqueue(x: T) = new SlowAppendQueue(elems ::: List(x))
}
class SlowHeadQueue[T](smele: List[T]) {
def head = smele.last
def tail = new SlowHeadQueue(smele.init)
def enqueue(x: T) = new SlowHeadQueue(x :: smele)
}
Is the following correct to say:
Both implementations of tail takes time proportional to the number of elements in the queue.
The second implementation of head is slower than the first. The second implementation takes time proportional to the length of the queue. Why is this? How is it implemented? Is it like a linked list where each element has a pointer to the next?
Why does Odersky say the second class' implementation of tail is problematic but not the first?
No. In the first case, tail works in constant time, because elems.tail is a constant time operation (it just returns the the tail of the list). The constructor new SlowAppendQueue(...) is also a constant time operation, because it just wraps the list.
Because if smele has N > 1 elements, then smele.init must rebuild a new list with N - 1 elements from scratch. This takes linear time, therefore it is much slower than the O(1) operation from the first queue implementation.
O(N) operations are problematic because they are slow for large N, whereas O(1) operations are essentially never problematic.
I think you should take a closer look into how the immutable single-linked list is implemented, and what it takes to prepend an element (O(1)), append an element (O(N)), to access the tail (O(1)), rebuild the init (O(N)). Then everything else becomes obvious.
No, the first tail implementation takes constant time. This is because List.tail is a constant time operation due to structural sharing, and wrapping the list in a new SlowAppendQueue is also a constant time operation.
The second implementation of head takes constant time because of the way functional linked lists (including Scala's List class) work. Each list node has a link to the node after it. In order to remove the last element via init, the entire list must be rebuilt.
In summary, List is fast when operating on the beginning, but not when solely operating on the end. See also the Scala docs for List.
Related
Does the time complexity depend on what is being matched on or does it get compiled down to some form of lookup table which can do O(1) lookups?
Some Scala's match statements can be compiled to the same byte code as Java's switch statements. There is an annotation to ensure that.
But, for most cases, especially complex ones like deconstructions, it will be compiled to the same byte code as a series of if / else statements.
In general, I would not expect them to be a "constant" operation, but rather a "linear" operation.
In any case, since the maximum number of checks will not change for the input, and usually it will not be more than a ten of them. Formally one will say it has O(1) complexity.
See yǝsʞǝlA's answer for a more detailed explanation of that.
If you are worried about it, you can put most common cases first and then the others. However, I would not really care about the performance of it, your application will not really notice it. And I would favor readability and correctness of the code instead.
Pattern matching in most cases will be O(1) because you are usually matching against a small number or possible cases and each match is comprised of a few constant time operations on average.
Since pattern matching is achieved by calling unapply method on the matched object docs and optionally comparing extracted values, the time complexity will depend on unapplys method's implementation and can be of any complexity. There is no compiler optimization possible for general case because some pattern matches depend on data being passed to them.
Compare these scenarios:
List(1, 2, 3) match {
case _ :+ last => ... // O(n) with respect to list length
case head :: tail => ... // O(1) w.r.t. list length
case _ => ... // O(1) - default case, no operation needs to be done
}
Most of the time we would pattern match something like a list to get head and tail split with :: - O(1) because unapply is simply returning head if it exists.
We usually don't use :+ because it's not common and expensive (library code):
/** An extractor used to init/last deconstruct sequences. */
object :+ {
/** Splits a sequence into init :+ last.
* #return Some((init, last)) if sequence is non-empty. None otherwise.
*/
def unapply[T,Coll <: SeqLike[T, Coll]](
t: Coll with SeqLike[T, Coll]): Option[(Coll, T)] =
if(t.isEmpty) None
else Some(t.init -> t.last)
}
To get last element of a sequence (t.last) we need to loop, which is O(n).
So it will really depend how you pattern match, but usually you pattern match case classes, tuples, options, collections to get first element and not last, etc. In such overwhelming majority of cases you'll be getting O(1) time complexity and a ton of type safety.
Additionally:
In the worst case here there will be m patterns each doing on average c operations to perform a match (this assumes unapply has constant time, but there are exceptions). Additionally there will be an object with n properties which we need to match against these patterns which gives us a total of: m * c * n operations. However, since m is really small (patterns never grow dynamically and usually written by a human) we can safely call it a constant b giving us: T(n) = b * c * n. In terms of big-O: T(n) = O(n). So we established theoretical bound of O(n) that is for cases where we need to check all n properties of an object. As I was pointing out above in most of the cases we don't need to check all properties/elements like when we use head :: tail where n is replaced with constant and we get O(1). It's only if we always do something like head :+ tail we would get O(n). Amortized cost I believe is still O(1) for all cases in your program.
EDIT, Summary: So, in the long chain of back and too, I think the "final answer" is a little hard to find. In essence however, Yuval pointed out that the incremental allocation of a large amount of memory forces a heap resize (actually, two by the look of the graph). A heap resize on a normal JVM involves a full GC, the most expensive, timeconsuming, collection possible. So, the reality is that my process isn't collecting lots of garbage per se, rather its doing heap resizes which inherently trigger expensive GC as part of the heap reorganization process. Those of us more familiar with Java than Scala are more likely to have allocated a simple ArrayList, which even if it causes heap resizing, is only a few objects (and likely allocated directly into old-gen if it's a big array) which would be far less work--because it's far fewer objects!--for a full GC anyway. Moral is likely that some other structure would be more appropriate for very large "lists".
I was trying to experiment with some of Scala's data structures (actually, with the parallel stuff, but that's not relevant to the problem I bumped into). I'm trying to create a fairly long list (with the intention of processing it purely sequentially). But try as I might, I'm failing to create a simple list without invoking vast quantities of garbage collection. I'm fairly sure that I'm simply pre-pending the new items to the existing tail, but the GC load suggests that I'm not. I've tried a couple of techniques so far (I'm starting to suspect that I'm misunderstanding something truly fundamental about this structure :( )
Here's the first effort:
val myList = {
#tailrec
def addToList(remaining:Long, acc:List[Double]): List[Double] =
if (remaining > 0) addToList(remaining - 1, 0 :: acc)
else acc
addToList(10000000, Nil)
}
And when I began to doubt I knew how to do recursion after all, I came up with this mutating beast.
val myList = {
var rv: List[Double] = Nil
var count = 10000000
while (count > 0) {
rv = 0.0 :: rv
}
rv
}
They both give the same effect: 8 cores running flat out doing GC (according to jvisualvm) and memory allocation reaching peaks at just over 1GB, which I assume is the real allocated space required for the data, but on the way, it creates a seemingly vast amount of trash on the way.
Am I doing something horribly wrong here? Am I somehow forcing the recreation of the entire list with every new element (I'm trying very hard to only do "prepend" type operations, which I thought should avoid that).
Or maybe, I have half a memory of hearing that Scala List does something odd to help it transform into a mutable list, or a parallel list, or something. Really don't recall what. Is this something to do with that? And if so, what the heck was "that" anyway?
Oh, and here's the image of the GC process. Notice the front-loading on the otherwise triangular rise of the memory that represents the "real" allocated data. That huge hump, and the associated CPU usage are my problem:
EDIT: I should clarify, I'm interested in two things. First, if my creation of the list is intrinsically faulty (i.e. if I'm not in fact only performing prepend operations) then I'd like to understand why, and how I should do this "right". Second, if my construction is sound and the odd behavior is intrinsic in the List, I'd like to understand the List better, so I know what it's doing, and why. I'm not particularly interested (at this point) in alternative ways to build a sequential data structure that sidesteps this problem. I anticipate using List a lot, and would like to know what's happening. (Later, I might well want to investigate other structures in this level of detail, but not right now).
First, if my creation of the list is intrinsically faulty (i.e. if
I'm not in fact only performing prepend operations) then I'd like to
understand why
You are constructing the list properly, there's no problem there.
Second, if my construction is sound and the odd behavior is intrinsic
in the List, I'd like to understand the List better, so I know what
it's doing, and why
List[A] in Scala is based on a linked list implementation, where you have a head of type A, and a tail of type List[A]. List[A] is an abstract class with two implementations, one presenting the empty list called Nil, and one called "Cons", or ::, representing a list which has a head value and a tail, which can be either full or empty:
def ::[B >: A] (x: B): List[B] =
new scala.collection.immutable.::(x, this)
If we look at the implementation for ::, we can see that it is a simple case class with two fields:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
override def tail : List[B] = tl
override def isEmpty: Boolean = false
}
A quick look using the memory tab in IntelliJ shows:
That we have ten million Double values, and ten million instances of the :: case class, which in itself has additional overhead for being a case class (the compiler "enhances" these classes with additional structure).
Your JVisualVM instance doesn't show the GC graph being fully utilized, it is rather showing your CPU is overworked from generating the large list of items. During the allocation process, you generate a lot of intermediate lists until you reach your fully generated list, which means data has to be evicted between the different GC levels (Eden, Survivor and Old, assuming you're running the JVM flavor of Scala).
If we want a bit more information, we can use Mission Control to see into what's causing the memory pressure. This is a sample generated from a 30 second profile running:
def main(args: Array[String]): Unit = {
def myList: List[Double] = {
#tailrec
def addToList(remaining:Long, acc:List[Double]): List[Double] =
if (remaining > 0) addToList(remaining - 1, 0 :: acc)
else acc
addToList(10000000, Nil)
}
while (true) {
myList
}
}
We see that we have a call to BoxesRunTime.boxToDouble which happens due to the fact that :: is a generic class and doesn't have a #specialized attribute for double. We go scala.Int -> scala.Double -> java.lang.Double.
The documentation says that Set.head returns the "first" item, and .tail returns "all but the first".* Since a Set doesn't really have a "first" item, the documentation warns that without an ordered type, you might get a different result on different runs. But are you guaranteed that the tail won't include the head?
The reason I'm asking is I'm wondering if it's OK to recurse down a Set like this:
def recurse(itemsToExamine: Set[Item], acc: Result): Result =
if (itemsToExamine.isEmpty) acc
else {
val item = itemsToExamine.head
recurse(
item.spawnMoreItems ++ itemsToExamine.tail,
acc.updatedFor(item))
}
If that's legitimate, it would certainly be nicer than converting from Set to Seq and back in order to separate head and tail on each recursion.
*Actually, it says "selects the first item" and "selects all but the first item". I assume that "selects" is just a poor choice of word. If there's a reason for saying "selects" rather than "returns", please let me know.
I'm not 100% sure about this, because I haven't looked too much at the implementation, but for any HashSet there's an implicit ordering based on the hashCode (of type Int) of the values that are already in the Set.
That means that for any Set instance, calls to head and tail will respect that ordering, so it won't be the same element. Even more, successive iteration through the elements of a given Set instance should yield the elements in the same order, because the Set is immutable.
The takeaway is that while the ordering is unknown, there is one for any instance, which may change as soon as you add (mutably or immutably) a new element to the Set.
Relying on head and tail on Set (without ordering) is risky at best.
In your case, simply get an Iterator from your Set first with theSet.toIterator, then recurse over the iterator. The iterator guarantees that the first element will be different from the others, of course.
You could do this:
val set = Set(1, 2, 3)
val head = set.head
val tail = set - head
This is guaranteed to keep them mutually exclusive.
listening to Scala courses and explanations I often hear: "but in real code we are not using recursion, but tail recursion".
Does it mean that in my Real code I should NOT use recursion, but tail recursion that is very much like looping and does not require that epic phrase "in order to understand recursion you first need understand recursion" .
In reality, taking into account your stack.. you more likely would use loop-like tail recursion.
Am I wrong? Is that 'classic' recursion is good only for education purposes to make your brain travel back to the university-past?
Or, for all that, there is place where we can use it.. where the depth of recursion calls is less than X (where X your stack-overflow limit). Or we can start coding from classic-recursion and then, being afraid of your stack blowing one day, apply couple of refactorings to make it tail-like to make use even stronger on refactoring field?
Question: Some real samples that you would use / have used 'classic head' recursion in your real code, which is not refactored yet into tail one, maybe?
Tail Recursion == Loop
You can take any loop and express it as tail-recursive call.
Background: In pure FP, everything must result in some value. while loop in scala doesn't result in any expression, only side-effects (e.g. update some variable). It exists only to support programmers coming from imperative background. Scala encourages developers to reconsider replacing while loop with recursion, which always result in some value.
So according to Scala: Recursion is the new iteration.
However, there is a problem with previous statement: while "Regular" Recursive code is easier to read, it comes with a performance penalty AND carries an inherent risk of overflowing the stack. On the other hand, tail-recursive code will never result in stack overflow (at least in Scala*), and the performance will be the same as loops (In fact, I'm sure Scala converts all tail recursive calls to plain old iterations).
Going back to the question, nothing wrong with sticking to the "Regular" recursion, unless:
The algorithm you are using in calculating large numbers (stack overflow)
Tail Recursion brings a noticeable performance gain
There are two basic kinds of recursion:
head recursion
Tail recursion
In head recursion, a function makes its recursive call and then performs some more calculations, maybe using the result of the recursive call, for example. In a tail recursive function, all calculations happen first and the recursive call is the last thing that happens.
The importance of this distinction doesn’t jump out at you, but it’s extremely important! Imagine a tail recursive function. It runs. It completes all its computation. As its very last action, it is ready to make its recursive call. What, at this point, is the use of the stack frame? None at all. We don’t need our local variables anymore because we’re done with all computations. We don’t need to know which function we’re in because we’re just going to re-enter the very same function. Scala, in the case of tail recursion, can eliminate the creation of a new stack frame and just re-use the current stack frame. The stack never gets any deeper, no matter how many times the recursive call is made. That’s the voodoo that makes tail recursion special in Scala.
Let's see with the example.
def factorial1(n:Int):Int =
if (n == 0) 1 else n * factorial1(n -1)
def factorial2(n:Int):Int = {
def loop(acc:Int,n:Int):Int =
if (n == 0) 1 else loop(acc * n,n -1)
loop(1,n)
}
Incidentally, some languages achieve a similar end by converting tail recursion into iteration rather than by manipulating the stack.
This won’t work with head recursion. Do you see why? Imagine a head recursive function. First it does some work, then it makes its recursive call, then it does a little more work. We can’t just re-use the current stack frame when we make that recursive call. We’re going to NEED that stack frame info after the recursive call completes. It has our local variables, including the result (if any) returned by the recursive call.
Here’s a question for you. Is the example function factorial1 head recursive or tail recursive? Well, what does it do? (A) It checks whether its parameter is 0. (B) If so, it returns 1 since factorial of 0 is 1. (C) If not, it returns n multiply by the result of a recursive call. The recursive call is the last thing we typed before ending the function. That’s tail recursion, right? Wrong. The recursive call is made, and THEN n is multiplied by the result, and this product is returned. This is actually head recursion (or middle recursion, if you like) because the recursive call is not the very last thing that happens.
For more info please refer the link
The first thing one should look at when developing software is the readability and maintainability of the code. Looking at performance characteristics is mostly premature optimization.
There is no reason not to use recursion when it helps to write high quality code.
The same counts for tail recursion vs. normal loops. Just look at this simple tail recursive function:
def gcd(a: Int, b: Int) = {
def loop(a: Int, b: Int): Int =
if (b == 0) a else loop(b, a%b)
loop(math.abs(a), math.abs(b))
}
It calculates the greatest common divisor of two numbers. Once you know the algorithm it is clear how it works - writing this with a while-loop wouldn't make it clearer. Instead you would probably introduce a bug on the first try because you forgot to store a new value into one of the variables a or b.
On the other side see these two functions:
def goRec(i: Int): Unit = {
if (i < 5) {
println(i)
goRec(i+1)
}
}
def goLoop(i: Int): Unit = {
var j = i
while (j < 5) {
println(j)
j += 1
}
}
Which one is easier to read? They are more or less equal - all the syntax sugar you gain for tail recursive functions due to Scalas expression based nature is gone.
For recursive functions there is another thing that comes to play: lazy evaluation. If your code is lazy evaluated it can be recursive but no stack overflow will happen. See this simple function:
def map(f: Int => Int, xs: Stream[Int]): Stream[Int] = f -> xs match {
case (_, Stream.Empty) => Stream.Empty
case (f, x #:: xs) => f(x) #:: map(f, xs)
}
Will it crash for large inputs? I don't think so:
scala> map(_+1, Stream.from(0).takeWhile(_<=1000000)).last
res6: Int = 1000001
Trying the same with Scalas List would kill your program. But because Stream is lazy this is not a problem. In this case you could also write a tail recursive function but generally this not easily possible.
There are many algorithms which will not be clear when they are written iteratively - one example is depth first search of a graph. Do you want to maintain a stack by yourself just to save the values where you need to go back to? No, you won't because it is error prone and looks ugly (beside from any definition of recursion - it would call a iterative depth first search recursion as well because it has to use a stack and "normal" recursion has to use a stack as well - it is just hidden from the developer and maintained by the compiler).
To come back to the point of premature optimization, I have heard a nice analogy: When you have a problem that can't be solved with Int because your numbers will get large and it is likely that you get an overflow then don't switch to Long because it is likely that you get an overflow here as well.
For recursion it means that there may be cases where you will blow up your stack but it is more likely that when you switch to a memory only based solution you will get an out of memory error instead. A better advice is to find a different algorithm that doesn't perform that badly.
As conclusion, try to prefer tail recursion instead of loops or normal recursion because it will for sure not kill your stack. But when you can do better then don't hesitate to do it better.
If you're not dealing with a linear sequence, then trying to write a tail-recursive function to traverse the entire collection is very difficult. In such cases, for the sake of readability/maintainability, you usually just use normal recursion instead.
A common example of this is a traversal of a binary tree data structure. For each node you might need to recur on both the left and right child nodes. If you were to try to write such a function recursively, where first the left node is visited and then the right, you'd need to maintain some sort of auxiliary data structure to track all the remaining right nodes that need to be visited. However, you can achieve the same thing just using the stack, and it's going to be more readable.
An example of this is the iterator method from Scala's RedBlack tree:
def iterator: Iterator[(A, B)] =
left.iterator ++ Iterator.single(Pair(key, value)) ++ right.iterator
I was reading about fold techniques in Programming in Scala book and came across this snippet:
def reverseLeft[T](xs:List[T]) = (List[T]() /: xs) {
(y,ys) => ys :: y
}
As you can see, it was done using foldLeft or /: operator. Curious how it would look like if I did it using :\, I came up with this:
def reverseRight[T](xs:List[T]) = (xs :\ List[T]()) {
(y,ys) => ys ::: List(y)
}
As I understand it, ::: doesn't seem to be as fast as :: and has a linear cost depending on the size of the operand list. Admittedly, I don't have a background in CS and no prior FP experience. So my questions are:
How do you recognise/distinguish between foldLeft/foldRight in problem approaches?
Is there a better way of doing this without using :::?
Since foldRight on List in the standard library is strict and implemented using linear recursion, you should avoid using it, as a rule. An iterative implementation of foldRight would be as follows:
def foldRight[A,B](f: (A, B) => B, z: B, xs: List[A]) =
xs.reverse.foldLeft(z)((x, y) => f(y, x))
A recursive implementation of foldLeft could be this:
def foldLeft[A,B](f: (B, A) => B, z: B, xs: List[A]) =
xs.reverse.foldRight(z)((x, y) => f(y, x))
So you see, if both are strict, then one or the other of foldRight and foldLeft is going to be implemented (conceptually anyway) with reverse. Since the way lists are constructed with :: associates to the right, the straightforward iterative fold is going to be foldLeft, and foldRight is simply "reverse then foldLeft".
Intuitively, you might think that this would be a slow implementation of foldRight, since it folds the list twice. But:
"Twice" is a constant factor anyway, so it's asymptotically equivalent to folding once.
You have to go over the list twice anyway. Once to push computations onto the stack and again to pop them off the stack.
The implementation of foldRight above is faster than the one in the standard library.
Operations on a List are intentionally not symmetric. The List data structure is a singly-linked list where each node (both data and pointer) are immutable. The idea behind this data structure is that you perform modifications on the front of the list by taking references to internal nodes and adding new nodes that point to them -- different versions of the list will share the same nodes for the end of the list.
The ::: operator which appends a new element on to the end of the list has to create a new copy of the entire list, because otherwise it would modify other lists that share nodes with the list you're appending to. This is why ::: takes linear time.
Scala has a data structure called a ListBuffer that you can use instead of the ::: operator to make appending to the end of a list faster. Basically, you create a new ListBuffer and it starts with an empty list. The ListBuffer maintains a list completely separate from any other list that the program knows about, so it's safe to modify it by adding on to the end. When you're finished adding on to the end, you call ListBuffer.toList, which releases the list into the world, at which point you can no longer add on to the end without copying it.
foldLeft and foldRight also share a similar assymmetry. foldRight requires you to walk the entire list to get to the end of the list, and keep track of everywhere you've visited on the way there, so that you an visit them in reverse order. This is usually done recursively, and it can lead to foldRight causing stack overflows on large lists. foldLeft on the other hand, deals with nodes in the order they appear in the list, so it can forget the ones it's visited already and only needs to know about one node at a time. Though foldLeft is also usually implemented recursively, it can take advantage of an optimization called tail recursion elimination, in which the compiler transforms the recursive calls into a loop because the function doesn't do anything after returning from the recursive call. Thus, foldLeft doesn't overflow the stack even on very long lists. EDIT: foldRight in Scala 2.8 is actually implemented by reversing the list and running foldLeft on the reversed list -- so the tail recursion issue is not an issue -- both data structures optimize tail recursion correctly, and you could choose either one (You do get into the issue now that you're defining reverse in terms of reverse -- you don't need to worry if you're defining your own reverse method for the fun of it, but you wouldn't have the foldRight option at all if you were defining Scala's reverse method.)
Thus, you should prefer foldLeft and :: over foldRight and :::.
(In an algorithm that would combine foldLeft with ::: or foldRight with ::, then you need to make a decision for yourself about which is more important: stack space or running time. Or you should use foldLeft with a ListBuffer.)