寻找最高效的方法简化分数

3

正如标题所示,我一直在尝试找到最有效的方法来简化一个分数 (例如 10/20 -> 1/2),我想到了这个方法。

def recursive(n, d):
    i=1
    loop = True

    while loop == True:
        i += 1
        if n % i == 0:
            if d % i == 0:
                loop = False

        if i > min(n, d):
            return f'{n} / {d}'

    nn = n // i
    nd = d // i
    return recursive(nn, nd), f'{n}/{d} Common D: {i}'

我的思路是,在最坏的情况下,这将计算分子和分母中的最小值 - n/d 在这个过程中进行(N) 次计算。而如果分数本身可以化简,它会这样做。这样做的同时,会减少 (N),也就是它需要一开始就进行的计数量。

例如,给定一个随机分数,比如206/202,它会立即将其除以二并再次调用该函数,新的调用只需要计数到101,因此大约只需进行(N/2)次计算。

为了测试这一点,我制作了一个控制函数,它总是计算 n/d 对的最小值,(因此总是进行N次计算),如下所示:

def big_O(n, d):
    list_ = [f'{n} / {d} OG']
    for iteration in range(2 ,min(n, d)+1):
        if n % iteration == 0:
            if d % iteration == 0:
                list_.append(f'{n//iteration} / {d//iteration} Common D: {iteration}')
    return list_

我使用了一个粗糙的计时器和一个随机数生成器对它们进行了时间比较:

这是我手头上的代码

def gen():
    return randint(1,1000), randint(1,1000)

然而,让我感到惊讶的是,我的函数表现得明显更差。我没有保存许多结果,但这里有一个结果相当具有代表性:
给定100,000个样本
简单:按速度排序
1. frac big O - 2.3903738830000023秒
2. frac recursive - 8.184171485秒
这让我感到震惊,因为我认为在最坏的情况下,由于运气不好得到了无法约分的分数,它们应该大致相等!所以我计算了生成器产生无法约分的分数的次数,结果大约是60%的时间,然后我想:
“好吧,如果我把它们都变成偶数,那么根据上述逻辑(206/202),我的函数肯定会更快。”
def gen_even():
    return randrange(2,2002,2), randrange(2,2002,2)

结果如下:

样本数为100,000

简单:按速度排序

  1. frac.big_O() -- 4.2074602940000005秒
  2. frac.recursive() -- 7.840177308999998秒

稍微好了一点...但仍然不够好!这怎么可能?我不明白。在此之后,我看了很多不同的东西,但最终还是没搞清楚。不过,我还是发现了一些有趣的事实,例如,一旦分数可以约分两次(即12/8:6/4:3/2),那么我的函数就会以更多的约分次数为因素变得更好。

例如,对于这个生成函数:

def genx4():
    return randint(1,1000)*4, randint(1,1000)*4

结果看起来是这样的:
100,000个样本
简单:按速度排序
1. frac recursive -- 6.605415995秒 2. frac big O -- 8.567245255秒
如果用*1000替换*4,它们看起来像这样
仅使用100个样本!
简单:按速度排序
1. frac recursive -- 0.014295906999997499秒 2. frac big O -- 2.250979277999999秒
但是为什么只在这里才有效呢?我不知道,所以如果任何读到这里的好心人有什么想说的话,从减少分数到为什么我的递归函数比上面更糟等不同可能的解决方案,请随意发表,同时非常感谢您的时间!

不要使用低劣的计时器,Python专门为此提供了timeit - Mark Ransom
1个回答

2
对于“最有效”的解决方案,您只需要查找n,d的最大公约数(GCD),因此欧几里得算法可以在O(log(max(n,d))乘法中解决。除非您是理论家或处理大量数字,否则可能没有必要过多优化。例如,请参见最大公约数维基百科以获取有关GCD计算的更多信息,但总结一下,您必须使用具有“数千位数”输入的快速乘法算法才能开始超越正常乘法。
对于令人惊讶的定时结果,这很可能是由于while循环开销与Python内部的for循环相比。尝试反汇编两个函数- for循环迭代是在C中完成的,而while循环迭代是在Python字节码中完成的,这会更慢。
在你所测量的短时间尺度上,性能可能会非常不可预测。因此,计时许多短循环可能会告诉您更多关于语言循环实现的效率,而不是您代码的算法效率。
当您的输入变大时,您只看到与渐近预测相符的结果并不令人惊讶--这是渐近分析的核心思想。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接