I have a seq of seqs in FSharp. I want to join a seq to the previous one if a predicate returns to true for it.
Sample:
le开发者_开发问答t items = seq [seq[2;3;4];seq[1;5;6;7;1;9];seq[2;3;5;7]]
I want to join a seq to the previos one, if the seq starts by 1, so the result should be in this case:
seq [seq[2;3;4;1;5;6;7;1;9];seq[2;3;5;7]
]
Is there any nice functional way to do it?
I am just started to translate my long computation processes from C# to F# and very impressed by the performance improvement I could achieve after even a very few hours of work and my beginner level knowledge of FSharp.
I have bought a book from Amazon entitled 'Beginning F#'. It is really great, but I mainly should work with seqs, lists, maps, collections now and this topic isn't explained as detailed as I need. Would anyone be so kind to advise me a good resource about ths topics?
Thx in advance!
let joinBy f input =
let i = ref 0
input
|> Seq.groupBy (fun x ->
if not (f x) then incr i
!i)
|> Seq.map (snd >> Seq.concat)
joinBy (Seq.head >> ((=) 1)) items
As with your last question, there is no library function that does exactly this. The most straightforward solution is to write this imperatively using IEnumerator
. However, you can write a more generally useful function (that can then be used for other purposes too).
module Seq =
/// Iterates over elements of the input sequence and groups adjacent elements.
/// A new group is started when the specified predicate holds about the element
/// of the sequence (and at the beginning of the iteration).
/// For example:
/// Seq.groupWhen isOdd [3;3;2;4;1;2] = seq [[3]; [3; 2; 4]; [1; 2]]
let groupWhen f (input:seq<_>) = seq {
use en = input.GetEnumerator()
let running = ref true
// Generate a group starting with the current element. Stops generating
// when it founds element such that 'f en.Current' is 'true'
let rec group() =
[ yield en.Current
if en.MoveNext() then
if not (f en.Current) then yield! group()
else running := false ]
if en.MoveNext() then
// While there are still elements, start a new group
while running.Value do
yield group() }
To solve the original problem, you can then check whether the first element of the sequence is a number other than 1. You'll get a sequence of groups where a group is sequence of sequences - then you can just concatenate the groups:
items
|> Seq.groupWhen (fun s -> Seq.head s <> 1)
|> Seq.map Seq.concat
EDIT: I also posted the function as a snippet (with nice F# formatting) here: http://fssnip.net/6A
As seen in the other solutions, this problem is almost the inverse of your last question. So for good measure, I give a modified version of my answer to that here:
let concatWithPreviousWhen f s = seq {
let buffer = ResizeArray()
let flush() = seq {
if buffer.Count > 0 then
yield Seq.readonly (buffer.ToArray())
buffer.Clear() }
for subseq in s do
if f subseq |> not then yield! flush()
buffer.AddRange(subseq)
yield! flush() }
And you use it like so:
seq [seq[2;3;4];seq[1;5;6;7;1;9];seq[2;3;5;7]]
|> concatWithPreviousWhen (Seq.head>>(=)1)
Looks like a fold to me as shown below. Tried to be as functional as possible without ref values.
let joinBy f (s:'a seq seq) =
let (a:'a seq), (b:'a seq seq) =
s |> Seq.fold (fun (a,r) se ->
if f se then (se |> Seq.append a,r)
else (se, seq {yield! r; yield a} ) )
(Seq.empty, Seq.empty)
seq {yield! b; yield a} |> Seq.filter (Seq.isEmpty >> not)
seq [seq[2;3;4];seq[1;5;6;7;1;9];seq[2;3;5;7]]
|> joinBy (Seq.head >> ((=) 1))
|> printfn "%A"
精彩评论