0

Possible Duplicate:
Linked list partition function and reversed results

Actually I don't care about the input type or the output type, any of seq, array, list will do. (It doesn't have to be generic) Currently my code takes list as input and (list * list) as output

let takeWhile predicator list =
   let rec takeWhileRec newList remain =
       match remain with
       | [] -> (newList |> List.rev, remain)
       | x::xs -> if predicator x then
                     takeWhileRec (x::newList) xs
                  else
                     (newList |> List.rev, remain)
   takeWhileRec [] list

However, there is a pitfall. As fas as I see, List.rev is O(n^2), which would likely to dominate the overall speed? I think it is even slower than the ugly solution: Seq.takeWhile, then count, and then take tail n times... which is still O(n)

(If there is a C# List, then i would use that without having to reverse it...)

A side question, what's difference between Array.ofList and List.toArray , or more generally, A.ofB and B.ofA in List, Seq, Array?

is seq myList identical to List.toSeq myList?

Another side question, is nested Seq.append have same complexity as Seq.concat?

e.g.

  Seq.append (Seq.append (Seq.append a b) c) d // looks aweful
  Seq.concat [a;b;c;d]
Community
  • 1
  • 1
colinfang
  • 20,909
  • 19
  • 90
  • 173
  • 1
    If `List.rev` really bugs you, see [my answer](http://stackoverflow.com/a/7199989/162396) to a similar question for a continuation-based version. – Daniel Oct 26 '12 at 14:25

3 Answers3

2

To answer your questions:

1) Time complexity of List.rev is O(n) and worst-case complexity of takeWhile is also O(n). So using List.rev doesn't increase complexity of the function. Using ResizeArray could help you avoid List.rev, but you have to tolerate a bit of mutation.

let takeWhile predicate list =
   let rec loop (acc: ResizeArray<_>) rest =
       match rest with       
       | x::xs when predicate x -> acc.Add(x); loop acc xs
       | _ -> (acc |> Seq.toList, rest)
   loop (ResizeArray()) list

2) There is no difference. Array.ofList and List.toArray uses the same function internally (see here and here).

3). I think Seq.concat has the same complexity with a bunch of Seq.append. In the context of List andArray, concat is more efficient than append because you have more information to pre-allocate space for outputs.

pad
  • 41,040
  • 7
  • 92
  • 166
2

1)The relevant implementation of List.rev is in local.fs in the compiler - it is

// optimized mutation-based implementation. This code is only valid in fslib, where mutation of private
// tail cons cells is permitted in carefully written library code.
let rec revAcc xs acc =
    match xs with
    | [] -> acc
    | h::t -> revAcc t (h::acc)

let rev xs =
    match xs with
    | [] -> xs
    | [_] -> xs
    | h1::h2::t -> revAcc t [h2;h1]

The comment does seem odd as there is no obvious mutation. Note that this is in fact O(n) not O(n^2)

2) As pad said there is no difference - I prefer to use the to.. as I think

A
|> List.map ...
|> List.toArray

looks nicer than

A
|> List.map ...
|> Array.ofList

but that is just me.

3)

Append (compiler source):

[<CompiledName("Append")>]
let append (source1: seq<'T>) (source2: seq<'T>) =
    checkNonNull "source1" source1
    checkNonNull "source2" source2
    fromGenerator(fun () -> Generator.bindG (toGenerator source1) (fun () -> toGenerator source2))

Note that for each append we get an extra generator that has to be walked through. In comparison, the concat implementation will just have 1 single extra function rather than n so using concat is probably better.

John Palmer
  • 25,356
  • 3
  • 48
  • 67
1

how about this:

let takeWhile pred =
    let cont = ref true
    List.partition (pred >> fun r -> !cont && (cont := r; r))

It uses a single library function, List.partition, which is efficiently implemented. Hope this is what you meant :)

Nikon the Third
  • 2,771
  • 24
  • 35
  • I doubt it is *efficient* because you always have to traverse the whole list and the overhead of `ref` cell. – pad Oct 26 '12 at 11:49
  • Just a word of caution -- in functional programming, the variable name `cont` is often used for a *continuation*, and it can be a bit confusing when it's used for some other variable. You might want to choose a different variable name next time (e.g., `finished` or `done`). – Jack P. Oct 26 '12 at 14:22