【问题标题】:Project Euler Q8 Largest product in a series - Why is my code wrong?Project Euler Q8 系列中最大的产品 - 为什么我的代码有误?
【发布时间】:2016-01-29 14:57:33
【问题描述】:

问题是(source)...

1000 位数字中相邻的四个数字最大 乘积为 9 × 9 × 8 × 9 = 5832。

73167176531330624919225119674426574742355349194934 96983520312774506326239578318016984801869478851843 85861560789112949495459501737958331952853208805511 12540698747158523863050715693290963295227443043557 66896648950445244523161731856403098711121722383113 62229893423380308135336276614282806444486645238749 30358907296290491560440772390713810515859307960866 70172427121883998797908792274921901699720888093776 65727333001053367881220235421809751254540594752243 52584907711670556013604839586446706324415722155397 53697817977846174064955149290862569321978468622482 83972241375657056057490261407972968652414535100474 82166370484403199890008895243450658541227588666881 16427171479924442928230863465674813919123162824586 17866458359124566529476545682848912883142607690042 24219022671055626321111109370544217506941658960408 07198403850962455444362981230987879927244284909188 84580156166097919133875499200524063689912560717606 05886116467109405077541002256983155200055935729725 71636269561882670428252483600823257530420752963450

找出 1000 位数字中相邻的 13 个数字 最伟大的产品。这个产品有什么价值?

我有以下 F#...

let largestProduct n (s : string) =
  [ for i in [0..(s.Length - n)] do yield s.[i..(i + n - 1)]]
  |> Seq.map (fun s -> s, s |> Seq.fold (fun p c -> p * (int (string c))) 1)
  |> Seq.maxBy snd

您将位数和 1000 位数作为字符串传递。第一行产生一个 n 字符的字符串序列,这些字符串通过管道传送到第二行,计算数字的乘积。这包含在一个带有 n 个字符的字符串的元组中,所以我可以看到哪一组 n 个字符产生了最高的产品。最后一行得到最大乘积。

如果我按如下方式运行...

largestProduct 4 nStr

...其中 nStr 是字符串形式的 1000 位数字,它产生以下...

("9989", 5832)

...这是正确的。但是,如果我将数字更改为 13,以解决实际问题,它会给我...

("9781797784617", 2091059712)

...这显然是错误的。

有人知道为什么我的代码不起作用吗?我已经尝试过各种小的 n 值,看起来它在那里工作。我也在较短的字符串上尝试过,它似乎工作正常。

【问题讨论】:

  • int 中可以存储而不会溢出的最大值是2147483647。虽然您的特定子字符串不会导致溢出,但可能是正确的子字符串。尝试使用 int64 代替,它可以存储 9^13,所以很安全。
  • @kvb 谢谢,我刚刚发现了同样的事情并回来发布答案

标签: f#


【解决方案1】:

这个练习会导致 Int32 溢出。任意长度类型bigint 为通常足够的输入范围解决了这个问题。例如:

let digitsToProduct inp =
    inp |> Seq.map (string >> bigint.Parse)
        |> Seq.fold (*) 1I

let largestProduct n : (seq<char> -> bigint) =
    Seq.windowed n >> Seq.map digitsToProduct >> Seq.max

编辑:注意largestProduct 采用第二个参数:1000 位数字的字符串(或任何字符序列)。

从中可以学到什么?

这是一个值得思考的基本问题。根据经验,一个函数应该是正确的还是失败的,至少对于合理的输入是这样。我认为,在开发人员可能犯此类错误的任何情况下,仅使用 64 位整数的答案都是不正确的。毕竟,它仍然会在输入太大时静默失败。

如果您想对此类函数使用 32 位或 64 位整数,请验证您的输入!

例如,粗略的验证可能是:

// 32b version
if n > 9 then invalidArg "n" "number of digits too large for Int32."
// 64b version
if n > 19L then invalidArg "n" "number of digits too large for Int64."

这会导致您的程序正常失败,而不是默默地产生无意义的结果。

【讨论】:

  • 我希望我能给你多于一票!您的回答是正确的,原因有几个。首先,它向我介绍了我以前从未见过的 Seq.windowed(对 F# 来说仍然相对较新),其次,您的 cmets 关于考虑域的想法是绝对正确的。碰巧,在发布我的答案后,我也产生了同样的想法,我认为整个练习对于那节课来说比我写的 F# 更有价值!
  • 再看一遍,我看不出你的代码是如何工作的。您将 1000 位数字传递到哪里?另外,你能解释一下最后一行是如何工作的吗?谢谢
  • @AvrohomYisroel 调用 largestProduct 13 inputDigits,其中 inputDigits 是一个字符串(或其他字符序列),仅包含 1000 个数字(无空格/换行符)。最后一行是三个函数的链:第一:从输入序列中构建 n 个字符的滑动窗口,第二:计算每个字符的数字乘积,第三:获得这些乘积的最大值。也许我使用函数组合是一个坏习惯,从而隐含了largestProduct的第二个参数。
  • 我尝试这样调用它,但是当我将 1000 位数字作为字符串传递时,出现错误“'string' 类型与 'seq' 类型不兼容”。另外,你能解释一下你最后一句话的意思吗?我仍然对在声明函数时不需要将字符串作为参数包含在内感到困惑。谢谢
  • @AvrohomYisroel 哦,我现在看到你的问题了。我将所有代码都放在一个 FSX 文件中,因此类型推断可以确定类型。我在我的答案中添加了一个类型注释,应该可以防止这个问题。 (或者,将两个函数都标记为inline。)我不需要将字符串作为参数包含在内的原因是该函数返回一个函数,然后该函数接受下一个参数。请注意,我使用&gt;&gt; 运算符进行函数组合;最后一行的结果表达式本身就是一个仍然以数字序列作为输入的函数。
【解决方案2】:

如您所见,使用int64 可以解决问题。

按照我阅读作业的方式,您不必返回导致最大乘积的数字;只需要产品本身。有了这个要求,实现就很容易了:

let largestProduct n : (string -> int64) =
    Seq.map (string >> System.Int64.Parse)
    >> Seq.windowed n
    >> Seq.map (Array.fold (*) 1L)
    >> Seq.max

如果你也想要数字序列,那也很简单:

let largestProductAndTheDigitsThatProduceIt n : (string -> string * int64) =
    Seq.map (string >> System.Int64.Parse)
    >> Seq.windowed n
    >> Seq.map (fun is -> System.String.Concat is , Array.fold (*) 1L is)
    >> Seq.maxBy snd

FSI:

> largestProductAndTheDigitsThatProduceIt 4 nStr;;
val it : string * int64 = ("9989", 5832L)
> largestProductAndTheDigitsThatProduceIt 13 nStr;;
val it : string * int64 = ("5576689664895", 23514624000L)

【讨论】:

  • 我为自己的目的添加了导致最大乘积的数字,并将它们留在最终代码中,因为它们没有造成任何伤害。感谢您的回复,它向我介绍了 Seq.windowed(以及 Vandroiy 的回答)。我仍在学习 F#,并发现这很有趣。
【解决方案3】:

在寻找灵感时,我遇到了一个问题,someone had the same problem

答案并不比乘法溢出 32 位整数的容量更复杂。当我将代码更改为使用 int64 时,它给出了正确的答案...

let largestProductInt64 (n : int64) (s : string) =
  [ for i in [0L..((int64 s.Length) - n)] do yield s.[(int i)..int(i + n - 1L)]]
  |> Seq.map (fun s -> s, s |> Seq.fold (fun p c -> p * (int64 (int (string c)))) 1L)
  |> Seq.maxBy snd

很遗憾,因为代码没有那么整洁,但它可以工作。

感谢@kvb,在我有机会发表自己的发现之前,他在评论中提出了同样的观点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 1970-01-01
    • 2019-10-07
    • 1970-01-01
    • 1970-01-01
    • 2018-10-21
    • 1970-01-01
    相关资源
    最近更新 更多