I have the following data:
[ 0, +1, +1, +1, 0, +1, -1, -1, 0, -1, -1, -1, -1, +1, +1, -1, +1, +1, +1, +1, +1, +1, +1, 0]
and this is the output I want:
[ 0, +1, +1, +1, 0, 0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, +1, +1, +1, +1, +1, +1, +1, 0]
in the source column, data can be +1, -1 or 0.
in the output, the +1s and -1s that have 3, or more, sequential occurrences can stay; the ones that do not, have to get converted to 0.
the Python/Pandas implementation is this:
s = df[data].where(df[data].groupby(df[data].ne(df[data].shift()).cumsum()).transform('size').ge(3), 0)
how can I implement this in F#?
I don't think there is any easy way to do this using built-in F# functions. However, if you define one helper operation, then you can solve this nicely.
The helper is a function that I'll call groupAdjacentBy
which groups together adjacent elements in the list based on some function that computes a grouping key. This is like List.groupBy
, except that it groups elements that have the same key only as long as they are next to each other.
val groupAdjacentBy : ('a -> 'b) -> 'a list -> 'a list list
The implementation of this for F# lists looks as follows:
module List =
let groupAdjacentBy f list =
let rec loop k group acc list =
match list with
| [] when List.isEmpty group -> List.rev acc
| [] -> List.rev ((List.rev group)::acc)
| x::xs when f x = k -> loop k (x::group) acc xs
| x::xs when not (List.isEmpty group) -> loop (f x) [x] ((List.rev group)::acc) xs
| x::xs -> loop (f x) [x] [] xs
if List.isEmpty list then []
else loop (f (List.head list)) [List.head list] [] (List.tail list)
Now you can solve your original problem by grouping adjacent elements that have the same value and replacing all groups smaller than 3 with groups of equal length containing zeros:
[ 0; +1; +1; +1; 0; +1; -1; -1; 0; -1; -1; -1; -1;
+1; +1; -1; +1; +1; +1; +1; +1; +1; +1; 0]
|> List.groupAdjacentBy id
|> List.map (fun group ->
if List.length group >= 3 then group
else List.map (fun _ -> 0) group)
|> List.concat