I am trying to understand the time complexity for these two functions. I have tried experimenting with both and here is what I have come up with
List.foldBack (@) [[1];[2];[3];[4]] [] => [1] @ List.foldBack (@) [[2];[3];[4]] []
=> [1] @ ([2] @ List.foldBack (@) [[3];[4]] [])
=> [1] @ ([2] @ ([3] @ List.foldBack (@) [4] []))
=> [1] @ ([2]@([3] @ ([4] @ List.foldBack[])))
=> [1]@([2]@([3]@([4]@([])))
=> [1; 2; 3; 4]
List.fold (@) [] [[1];[2];[3];[4]]
=> List.fold (@) (([],[1])@ [2]) [[3];[4]]
=> List.fold (@) ((([]@[1])@[2])@[3]) [[4]]
=> List.fold (@) (((([]@[1])@[2])@[3])@[4]) []
=> (((([]@[1])@[2])@[3])@[4])
Now it seems to me that they are both linear since it takes the same amount of calculations to achieve the same result. Am i correct or is there something that I am missing?
If each inner operation is Θ(1),
List.foldandList.foldBackis O(n) wherenis the length of the list.However, to estimate asymptotic time complexity, you need to rely on Θ(1) operations. In your example, things are a little more subtle.
Suppose you need to concatenate
nlists where each list hasmelements. Since@isO(n)of the length of the left operand, we have complexity offoldBack:and that of
fold:Therefore, with your naive way of using
@,foldBackis linear whilefoldis quadratic to the size of input lists.It is worth to note that
@is associative (a @ (b @ c) = (a @ b) @ c); therefore, results are the same forfoldandfoldBackin this case.In practice, if the inner operator is non-associative, we need to choose the right order by either using
foldorfoldBack. AndList.foldBackin F# is made tail-recursive by transforming lists to arrays; there are some overheads by this operation as well.