Warm tip: This article is reproduced from stackoverflow.com, please click
f#

how to keep only 'stable' values in a list in F#

发布于 2020-04-08 09:23:56

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#?

Questioner
Thomas
Viewed
49
Tomas Petricek 2020-01-31 08:23

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