问题
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]
回答1:
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.
回答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.
回答3:
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 :)
来源:https://stackoverflow.com/questions/13085575/some-basic-seq-and-list-questions