5

在 APL 中,可以使用一个位向量来选择另一个向量的元素;这称为压缩。例如 1 0 1/3 5 7 将产生 3 7。

在一般的函数式编程中,特别是在 F# 中是否有一个公认的术语?

这是我的 F# 程序:

let list1 = [|"Bob"; "Mary"; "Sue"|]
let list2 = [|1; 0; 1|]

[<EntryPoint>]
let main argv = 

    0 // return an integer exit code

我想做的是计算一个新的字符串[],它是 [|"Bob"; 苏"|]

在 F# 中如何做到这一点?

4

4 回答 4

6
Array.zip list1 list2                 // [|("Bob",1); ("Mary",0); ("Sue",1)|]
|> Array.filter (fun (_,x) -> x = 1)  // [|("Bob", 1); ("Sue", 1)|]
|> Array.map fst                      // [|"Bob"; "Sue"|]

管道运算符|>在语法上颠倒了函数应用程序,即x |> f等效于f x. 如另一个答案中所述,替换ArraySeq以避免构建中间数组。

我希望您会发现 F# 中缺少许多 APL 原语。对于列表和序列,许多可以通过将来自 、 或 模块的原语串在一起来构造SeqArray就像List上面一样。作为参考,这里是该模块的概述。Seq

于 2014-03-30T09:38:06.730 回答
1

我认为最简单的是使用数组序列表达式,如下所示:

let compress bits values =
    [|
        for i = 0 to bits.Length - 1 do
            if bits.[i] = 1 then
                yield values.[i]
    |]

如果您只想使用组合器,我会这样做:

Seq.zip bits values
|> Seq.choose (fun (bit, value) ->
    if bit = 1 then Some value else None)
|> Array.ofSeq

我使用Seq函数而不是Array为了避免构建中间数组,但这也是正确的。

于 2014-03-30T09:40:16.393 回答
1

有人可能会说这更惯用:

Seq.map2 (fun l1 l2 -> if l2 = 1 then Some(l1) else None) list1 list2
|> Seq.choose id
|> Seq.toArray

编辑(对于烟斗爱好者)

(list1, list2)
||> Seq.map2 (fun l1 l2 -> if l2 = 1 then Some(l1) else None)
|> Seq.choose id
|> Seq.toArray
于 2014-03-30T11:07:05.530 回答
0

Søren Debois 的解决方案很好,但正如他所指出的,但我们可以做得更好。让我们根据 Søren 的代码定义一个函数:

let compressArray vals idx =
    Array.zip vals idx
        |> Array.filter (fun (_, x) -> x = 1)
        |> Array.map fst

compressArray最终在 3 行中的每一行中创建一个新数组。如果输入数组很长(在我的快速测试中,10M 值需要 1.4 秒),这可能需要一些时间。
我们可以通过处理序列并仅在最后创建一个数组来节省一些时间:

let compressSeq vals idx =
    Seq.zip vals idx
        |> Seq.filter (fun (_, x) -> x = 1)
        |> Seq.map fst

此函数是通用函数,适用于数组、列表等。要生成一个数组作为输出:

compressSeq sq idx |> Seq.toArray

后者节省了大约 40% 的计算时间(在我的测试中为 0.8 秒)。

正如 ildjarn 评论的那样,可以将函数参数filter重写为snd >> (=) 1,尽管这会导致性能略有下降(< 10%),这可能是因为生成了额外的函数调用。

于 2014-03-31T08:41:16.077 回答