F#:分区函数就像Clojure中的那样

时间:2016-02-04 19:54:43

标签: clojure f#

Clojure有一个很好的函数叫做分区,它可以处理序列。它将给定的序列分成一系列同样长的列表。第一个参数指定fragaments的长度。第二个参数是一个偏移量,它指定片段的下一个开始。

(partition 3 1 (range 5))
;;=> ((0 1 2) (1 2 3) (2 3 4))

(partition 4 6 (range 20))
;;=> ((0 1 2 3) (6 7 8 9) (12 13 14 15))

(partition 4 3 (range 20))
;;=> ((0 1 2 3) (3 4 5 6) (6 7 8 9) (9 10 11 12) (12 13 14 15) (15 16 17 18))

https://clojuredocs.org/clojure.core/partition

我正在寻找F#中的等效函数。显然List.partition做了别的事情(https://msdn.microsoft.com/en-us/library/ee353782.aspx)。也许有一个图书馆提供这样的功能?

2 个答案:

答案 0 :(得分:7)

在F#中,你有两个相似的函数:windowed,它类似于Clojure的分区,但第二个参数固定为1,chunkBySize,第二个参数等于第一个参数。

您可以将两者结合起来并获得所需的功能。以下是列表示例:

let partition x y = List.windowed x >> List.chunkBySize y >> List.map List.head

它们也可用于数组和序列,但请注意,对于序列,内部集合将是一个数组,实际上是一个序列。因此,如果您希望将结果严格推断为序列序列,则必须添加转换或向上转换:

let partition x y = Seq.windowed x >> Seq.chunkBySize y >> Seq.map (Seq.head >> seq)

答案 1 :(得分:2)

你可以自己实现这个功能,正如@Gustavo所证明的那样,这并不难。一个问题是你刚刚完成的功能是否正常工作。

所以这是一个使用wait来测试分区函数hold的属性的实现和属性测试。

FsCheck是一个很棒的工具,可以检查您刚刚实现的功能是否具有正确的属性。

open FsCheck

let partition (size : int) (increment : int) (vs : 'T []) : 'T [] [] =
  let size      = max 1 size
  let increment = max 1 increment
  let length    = vs.Length
  [| for i in [size..increment..length] -> vs.[(i - size)..(i - 1)] |]

let range (n : int) : int [] = [| 0..(n - 1) |] 

type Properties() =

  static member ``all partitions have the right size`` (size : int, increment : int, vs : int []) =
    (size > 1 && increment > 1 && vs.Length > 1) ==> 
      fun () ->
        let partitions = partition size increment vs
        // Iterates over partitions and make sure they have the same Length = size
        let rec check = function
          | i when i < 
            partitions.Length -> partitions.[i].Length = size && (check (i + 1))
          | _ -> true
        check 0

  static member ``all partitions have been incremented the right way`` (size : int, increment : int, vs : int []) =
    (size > 1 && increment > 1 && vs.Length > 1) ==>
      fun () ->
        let ivs = vs |> Array.mapi (fun i v -> (i,v))
        let partitions = partition size increment ivs
        // Iterates over partitions and make sure the first element has the right increment
        let rec check = function
          | i when i < partitions.Length -> 
            let ii, vv = partitions.[i].[0]
            ii = (i*increment) && vv = vs.[ii]  && (check (i + 1))
          | _ -> true
        check 0

  static member ``all partitions have the right content`` (size : int, increment : int, vs : int []) =
    (size > 1 && increment > 1 && vs.Length > 1) ==> 
      fun () ->
        let ivs = vs |> Array.mapi (fun i v -> (i,v))
        let partitions = partition size increment ivs
        // Iterates over partitions and make sure the each in the partition is correct element
        let rec check = function
          | i when i < partitions.Length -> 
            let partition = partitions.[i]
            let exp       = i*increment
            let rec check_partition = function
              |  i when i < partition.Length -> 
                let ii, vv = partition.[i] 
                ii = (exp + i) && vv = vs.[ii] && check_partition (i + 1)
              | _ -> true
            check_partition 0 && (check (i + 1))
          | _ -> true
        check 0

[<EntryPoint>]
let main argv = 
  range 5   |> partition 3 1 |> printfn "%A"
  range 20  |> partition 4 6 |> printfn "%A"
  range 20  |> partition 4 3 |> printfn "%A"

  let config = { Config.Quick with MaxFail = 100000; MaxTest = 1000 }

  // Generates random data and make sure the properties holds for any random data
  Check.All<Properties> config

  0

所以即使你没有要求这个,我想也许你对它感兴趣。