获取数字数字的最有效方法是什么?
让我们从一个例子开始:
想象一下Fibonacci序列。现在让我们说我们想知道哪个Fibonacci数是第一个有1000个数字(以10为基数表示)。最多308位数(第1476个Fibonacci数)我们可以使用logBase 10 <number>
轻松完成此操作。如果该数字大于第1476个Fibonacci数,logBase
将返回Infinity
,计算将失败。问题是308距离1000有点远,这是我们最初的目标。
一种可能的解决方案是将我们想要知道的数字转换为字符串的数字,并使用它的长度来确定数字计数。对于我的目的来说这有点效率低,因为用10000来尝试它会花费它的甜蜜时间。
other questions中显示的最有效的方法是硬编码我确实不想做的所有可能情况,特别是因为在建议的解决方案中需要的位数超过10。
回到我的问题:确定基数为10的数字计数的最佳(最有效)方法是什么?是真的将它转换为字符串并使用它的长度,还是有任何&#34;黑客&#34;像0x5f3759df这样的技巧?
注意:我很欣赏任何语言的解决方案,即使它被标记为&#34; haskell&#34;。
答案 0 :(得分:7)
为什么不使用div
,直到它不再大于10?
digitCount :: Integer -> Int
digitCount = go 1 . abs
where
go ds n = if n >= 10 then go (ds + 1) (n `div` 10) else ds
O(n)
复杂度为n
,其中1000
是位数,您可以通过查看100
,然后10
,然后{{}来轻松加快速度。 1}},但这可能足以满足大多数用途。
作为参考,在我不太好的笔记本电脑上运行它只在GHCi中运行并使用非常不准确的:set +s
统计标记:
> let x = 10 ^ 10000 :: Integer
> :force x
<prints out 10 ^ 10000>
> digitCount x
10001
it :: Int
(0.06 secs, 23759220 bytes)
所以看起来非常快,它可以在不到10秒的时间内通过一个10001位的数字而无需优化。
如果你真的想要O(log(n))
的复杂性,我建议你写一个你自己的版本,每次除以2,但是这个版本比10除以10更多参与和棘手。为了你的目的这个版本将轻松计算最多约20000位数字的位数而没有问题。
答案 1 :(得分:2)
如果您只想查找列表中至少有digitCount
位数的第一个数字,可以通过检查O(1)
来测试fibBeingTested >= 10digitCount - 1
中的每个数字。这是有效的,因为10digitCount - 1
是具有至少digitCount
位数的最低数字:
import Data.List (find)
fibs :: [Integer]
-- ...
findFib :: Int -> Integer
findFib digitCount =
let Just solution = find (>= tenPower) fibs
in
solution
where
tenPower = 10 ^ (digitCount - 1)
我们使用digitCount - 1
,因为10^1
是10
,有两位数。
由于此比较具有O(1)
复杂性,您可以非常快速地找到斐波那契数字。在我的机器上:
λ> :set +s
λ> findFib 10000
[... the first Fibonacci number with at least 10,000 digits ...]
(0.23 secs, 121255512 bytes)
如果fibs
的列表已经计算到10,000th
数字斐波纳契(例如,如果你运行findFib 10000
两次),它的速度会更快,这表明更多的计算是计算每个斐波纳契数而不是找到你要找的那个:
λ> findFib 10000 -- Second run of findFib 10000
[... the first Fibonacci number with at least 10,000 digits ...]
(0.04 secs, 9922000 bytes)
答案 2 :(得分:0)
如果只是为了获得超过1000位的斐波那契数字,length . show
(Integer
}就足够了。
GHCi> let fibs = Data.Function.fix $ (0:) . scanl (+) 1
GHCi> let digits = length . (show :: Integer -> String)
GHCi> :set +t +s
GHCi> fst . head . dropWhile ((1000>) . digits . snd) $ zip [0..] fibs
4782
it :: Integer
(0.10 secs, 149103264 bytes)
对于浮点数(因此可以使用logBase),在Double numbers
包的范围之外。它们的速度很慢,但你必须为这种准确性付出代价。
答案 3 :(得分:0)
您总是可以尝试使用二进制搜索来查找 n 的位数:首先找到 k ,使得10 ^ 2 ^k≥n,然后除以n连续10 ^ 2 ^(k-1),10 ^ 2 ^(k-2),...,10 ^ 2 ^ 0:
numDigits n = fst $ foldr step (1,n) tenToPow2s
where
pow2s = iterate (*2) 1
tenToPow2s = zip pow2s . takeWhile (<=n) . iterate (^2) $ 10
step (k,t) (d,n) = if n>=t then (d+k, n `div` t) else (d,n)
对于Fibonacci数的特定情况,您也可以尝试数学: n -th Fibonacci数F(n)介于(φ^ n-1)/√5和(φⁿ+之间) 1)/√5所以对于基数10的对数,我们有:
log(F(n)) - n log(φ)+ log(√5)∈[log(1 - 1 /φⁿ),log(1 + 1 /φⁿ)]
这个间隔很快就会消失。