从F#中的lambda表达式中产生

时间:2013-06-15 22:11:46

标签: .net f#

我在F#中实现了一个标准的可变就地置换算法(如果有一些类似的内置方法,我会很感激这些信息):

let permutations f (alphabet:'a array) =
    let swap i j =
        let aux = alphabet.[i]
        alphabet.[i] <- alphabet.[j]
        alphabet.[j] <- aux
    let rec permutations' n =
        if n = alphabet.Length
        then f alphabet
        else
            for i in n..(alphabet.Length-1) do
                swap n i
                permutations' (n+1)
                swap n i
    permutations' 0

虽然这个函数非常通用,但我想知道在F#中是否有某种方法可以实现一个包装器函数,它可以将发现的项目作为序列生成。类似于以下(不正确的)F#方法:

let permutations_seq (alphabet:'a array) =
    seq {
        permutations (fun arr -> yield arr.Clone()) alphabet
    }

permutations中,我不想直接yield,因为我想维护一般功能,我不希望客户端总是为阵列克隆付出代价。

你会怎么做?

3 个答案:

答案 0 :(得分:3)

如果你想从lambda函数“产生”结果,那么lambda函数本身需要返回一个序列(因此lambda函数的调用者也需要返回一个序列)。因此,如果不修改permutations函数,就无法获得所需的内容(因为您无法将在一个(嵌套)作用域中运行的代码的值生成到某些其他(外部)作用域中定义的列表)。

但是,您可以将permutations更改为如下所示:

let permutations f (alphabet:'a array) =
    let swap i j =
        let aux = alphabet.[i]
        alphabet.[i] <- alphabet.[j]
        alphabet.[j] <- aux
    let rec permutations' n = seq {
        if n = alphabet.Length
        then yield! f alphabet
        else
            for i in n..(alphabet.Length-1) do
                swap n i
                yield! permutations' (n+1)
                swap n i }
    permutations' 0

我将permutations'包裹在seq { .. }块中,并在yield!之前添加f alphabet(以便将函数f生成的所有元素作为结果传递)我还在递归调用中添加了yield!

然后你可以写:

permutations (fun arr -> seq { yield arr |> Array.map id }) [|1;2;3|]

代码使用的是Array.map id而不是Clone,因此您可以获得数组的类型安全副本,而不是.NET克隆机制返回的obj

但是,我想你实际上并不需要从lambda中产生多个项目,所以你可以将yield! f alphabet改为yield f alphabet(只返回一个元素而不是多个元素)和写:

permutations (fun arr -> arr |> Array.map id) [|1;2;3|]

通过这种方式,您 - 至少 - 可以很好地抽象出克隆行为(您可以选择克隆或不克隆数组)。

答案 1 :(得分:1)

您必须生成未克隆的数组。有明显的 奇怪的行为,如果你在序列上调用toList 然后你得到一个数组的最后一个值的数组。所以 你需要做的第一件事就是Seq.map 克隆功能。我也认为没有必要 如果你已经准备好了你的函数递归 使用mutables。

let permutations (alphabet:'a array) =
    let swap i j =
        let aux = alphabet.[i]
        alphabet.[i] <- alphabet.[j]
        alphabet.[j] <- aux
    let rec permutations' n = 
        seq {
                if n = alphabet.Length
                then yield alphabet
                else
                    for i in n..(alphabet.Length-1) do
                        swap n i
                        yield! permutations' (n+1)
                        swap n i
        }
    permutations' 0


let a = [|"a"; "b"; "c"; "d"|]
let p =
    (permutations a)
    |> Seq.map (fun arr -> arr.Clone() )
    |> Seq.toList

输出

  val p : obj list =
  [[|"a"; "b"; "c"; "d"|]; [|"a"; "b"; "d"; "c"|]; [|"a"; "c"; "b"; "d"|];
   [|"a"; "c"; "d"; "b"|]; [|"a"; "d"; "c"; "b"|]; [|"a"; "d"; "b"; "c"|];
   [|"b"; "a"; "c"; "d"|]; [|"b"; "a"; "d"; "c"|]; [|"b"; "c"; "a"; "d"|];
   [|"b"; "c"; "d"; "a"|]; [|"b"; "d"; "c"; "a"|]; [|"b"; "d"; "a"; "c"|];
   [|"c"; "b"; "a"; "d"|]; [|"c"; "b"; "d"; "a"|]; [|"c"; "a"; "b"; "d"|];
   [|"c"; "a"; "d"; "b"|]; [|"c"; "d"; "a"; "b"|]; [|"c"; "d"; "b"; "a"|];
   [|"d"; "b"; "c"; "a"|]; [|"d"; "b"; "a"; "c"|]; [|"d"; "c"; "b"; "a"|];
   [|"d"; "c"; "a"; "b"|]; [|"d"; "a"; "c"; "b"|]; [|"d"; "a"; "b"; "c"|]]

答案 2 :(得分:1)

如果您真的想使用回叫(reactive)方法,请使用 反应性扩展

https://github.com/fsharp/FSharp.Reactive/blob/master/src/Observable.fs

并写

let permutations (alphabet:'a array) =
    Observable.create (fun subscriber ->
      let swap i j =
          let aux = alphabet.[i]
          alphabet.[i] <- alphabet.[j]
          alphabet.[j] <- aux
      let rec permutations' n = 
          seq {
                  if n = alphabet.Length
                  then subscriber.OnNext(alphabet)
                  else
                      for i in n..(alphabet.Length-1) do
                          swap n i
                          permutations' (n+1)
                          swap n i
          }
      permutations' 0
    )

然后你可以做

permutations [|"a"; "b"; "c"|]
|> Observable.map ( fun arr -> arr.Clone() )
|> Observable.ToEnumerable
|> Seq.ToList

然而请注意与我发布的其他答案相关的对称性 在收益率上,所以在这种情况下你不会获得高于收益率的收益。