【问题标题】:F# - Return a value from for .. doF# - 从 for .. 返回一个值
【发布时间】:2016-04-23 20:49:49
【问题描述】:

我有一组(随机)数字,我想确定任何给定数字,该数字是否是复合数字(意味着它可以由同一给定集合中的其他两个数字创建)。
我的函数的数学定义:

当前代码:

let task1 (M : seq<'T>, r : 'T) : bool =
    if not(Seq.exists((=) r) M) then
        failwith "nope!"
    else
        for s in M do
            for t in M do
                if s + t = r then
                    true         // error
        false

问题:
当元素 r 被“找到”为两个元素 st 的总和时,我无法从迭代中返回布尔结果。


我怎样才能尽可能有效地解决给定的问题?

如果有人发现算法的 total 运行时间小于 O(n²),那么我也可以。
[所有被调用的方法也必须比 O(n²) 快]

【问题讨论】:

  • 这是偶然的功课吗?当有人要求一个对大 O 有限制的算法时,我只需要问。 O(n2) 对于这个问题是显而易见的。
  • @GuyCoder:嗯,这是一项任务,可能会在以后的考试中出现,我目前正在尝试用 F# 编写它;)谢谢您的第二条评论,先生 - 我意识到我忘记了not
  • 在这种情况下,使用 Seq.find 代替循环
  • 且无需注解类型,只需指定inline
  • @FoggyFinder:Seq.find 很整洁,不过我会看看它的性能...

标签: loops f# iteration big-o


【解决方案1】:

你不能做你想做的事,因为 F# 是一种使用表达式而不是语句的语言。 F# 表达式总是计算出一个值(尽管该值可能是单位:())。

您最初发布的代码无法编译,因为需要 unit 类型的东西,那是因为您的 if/then 表达式没有 else 分支。考虑以下几点:

let a = if x > 5 then 10

这段代码会产生一个编译器错误,这很明显,因为我们没有指定如果x 不大于5,a 的整数值可能是什么。

当然,这段代码会编译:

let a = if x > 5 then 10 else 5

如果您提供和if 而不提供else,F# 编译器将假定类型为单位,因此这也是有效代码:

let a = if x > 5 then ()

这是因为这两种情况仍然只返回unit,没有类型不匹配。

因为 F# 使用表达式,所以一切都必须绑定到一个值。


无论如何,您可以使用嵌套的Seq.exists 语句来解决这个问题,这样您就可以检查值的每个组合。

let inline task1 items r =
    items |> Seq.exists (fun s ->
        items |> Seq.exists(fun t -> s + t = r))

我已经更改了您的一些命名约定(F# 函数参数的习惯用法是驼峰式大小写),并且已将您的函数设为 inline,因此它适用于任何支持加法的类型。

【讨论】:

    【解决方案2】:

    通常,您不希望在 F# 中使用循环来实现复杂的迭代/递归函数。这就是尾递归函数的好处。

    要获得低于 O(n²) 的计算复杂度,如何:考虑候选和数集,它首先是完整的数字集。现在看看这个集合中最大和最小数字的总和。如果此总和大于目标数,则最大数不会与任何目标数相加。反过来也是一样。

    不断从候选集合中删除最小或最大的数字,直到集合为空或找到匹配项。

    代码可能如下所示:

    let rec f M x =
        if Set.isEmpty M then false else
        let biggest, smallest = Set.maxElement M, Set.minElement M
        let sum = biggest + smallest
        if   sum > x then f (Set.remove biggest M)  x
        elif sum < x then f (Set.remove smallest M) x
        elif sum = x then true
        else failwith "Encountered failure to compare numbers / NaN"
    

    当我查看数学定义时,似乎并不要求目标数在集合中。但如果这很重要,请事先检查:

    let fChecked M x =
        if Set.contains x M then f M x
        else invalidArg "x" "Target number is not contained in set."
    

    Tom 将此泛型化为可比类型,该函数可以标记为inline

    集合操作是 O(log n),我们遍历整个事情一次,乘以 O(n),使得总复杂度 O(n log n)(其中 n 是集合中的元素)。


    有效极速版

    如果它是关于实际速度而不是“仅仅”计算复杂性,那么数组可能比不可变集合更快。这是一个使用数组索引的版本:

    /// Version where M is types as an unsorted array we're allowed to sort
    let f M x =
        Array.sortInPlace M
        let rec iter iLow iHigh =
            if iLow > iHigh then false else
            let sum = M.[iLow] + M.[iHigh]
            if   sum > x then iter iLow (iHigh - 1)
            elif sum < x then iter (iLow + 1) iHigh
            elif sum = x then true
            else failwith "Encountered failure to compare numbers / NaN"
        iter 0 (M.Length - 1)
    

    请注意,如果重复使用同一个集合,我们可以对数组进行预排序,从而将同一集合上每次调用的复杂度降低到 O(n)

    【讨论】:

    • 我非常喜欢这个解决方案,虽然我认为我更喜欢@TheInnerLight 的解决方案,因为它很简单......,但我认为你的解决方案有更好的性能:)
    • @Unknown6656 好吧,您要求的计算复杂度小于 O(n²)。我认为这将是更有趣的练习。 :P
    • 你说得对,先生。但是,我明天会接受一个答案(还没有),让其他人有机会发布其他“练习”;)
    • @GuyCoder 看来它毕竟可以变得更简单,你提到的平衡树给了我这个想法。 F# Set 类型内部基于平衡树,支持在 O(log n) 中查找最大/最小元素。我编辑了答案,该函数现在更短了,应该产生相同的结果。也许我会再次添加有关数组/快速版本的注释?嗯。很高兴你喜欢这个答案! :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-31
    相关资源
    最近更新 更多