[Python小项目] 冰雹猜想

冰雹猜想

一、前言

1.1 冰雹猜想是什么?

一个正整数x,如果是奇数就乘以3再加1,如果是偶数就直接除以2,这样经过若干次数,这个数最终会回到1。
无论这个过程中的数值如何庞大,最终都会像瀑布一样迅速坠落。在经过若干次的变换之后也必然会到纯偶数:4-2-1的循环之中。据日本和美国的数学家攻关研究,在小于7*10^11的所有的正整数中,都符合这个规律。

1.2 冰雹猜想来历

1976年的一天,《华盛顿邮报》于头版头条报道了一条数学新闻。文中记叙了这样一个故事:
70年代中期,美国各所名牌大学校园内,人们都像发疯一般,夜以继日,废寝忘食地玩弄一种数学游戏。这个游戏十分简单:任意写出一个正整数N,并且按照以下的规律进行变换:
如果是个奇数,则下一步变成3N+1。
如果是个偶数,则下一步变成N/2。
不单单是学生,甚至连教师、研究员、教授与学究都纷纷加入 。为什么这种游戏的魅力经久不衰?因为人们发现,无论N是怎样一个数字,最终都无法逃脱回到谷底1。准确地说,是无法逃出落入底部的4-2-1循环,永远也逃不出这样的宿命。
这就是著名的冰雹猜想
冰雹的最大魅力在于不可预知性。英国剑桥大学教授John Conway找到了一个自然数27。虽然27是一个貌不惊人的自然数,但是如果按照上述方法进行运算,则它的上浮下沉异常剧烈:
首先,27要经过77步骤的变换到达顶峰值:9232,然后又经过34步骤到达谷底值1。
全部的变换过程(称作雹程)需要111步,而其顶峰值9232,达到了原有数字27的342倍多,如果以瀑布般的直线下落(2的N次方)来比较,则具有同样雹程的数字N要达到2的111次方。其对比何其惊人!
若2的N次方=该冰雹数字,那么该冰雹数字的雹程除以N,这里小编称为雹程比,因为通过该雹程比来判断一个冰雹数字在高处抖动的剧烈程度更加合理。

1.3 一些数字的雹程序列

2: [2, 1],
3: [3, 10, 5, 16, 8, 4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
7: [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
11: [11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
13: [13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
17: [17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]}
27的雹程序列
27: [27, 82, 41, 124, 62, 31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
雹程:111
雹程比:23.34
顶锋值:9232
顶峰值位置:77
倍率:341.93

二、代码

2.1 代码实现功能

经过前面的介绍,相信大家已经明白了冰雹猜想及相关概念。接下来,小编将通过Python进行开发,来计算数字的雹程序列并进行相关分析。
代码将实现以下3个功能:
1、计算开始数字到结束数字的所有雹程序列并保存到字典中。比如从2到10之间,每个数字的雹程序列:

{
2: [2, 1],
3: [3, 10, 5, 16, 8, 4, 2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
6: [6, 3, 10, 5, 16, 8, 4, 2, 1],
7: [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
9: [9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
10: [10, 5, 16, 8, 4, 2, 1]
}
2、计算开始数字到结束数字的雹程序列分析结果并保存在字典中,包含3个分析结果:
①、雹程
②、雹程比
③、顶峰值
④、顶峰值位置
⑤、倍率
比如从2到10之间,每个数字的雹程分析结果如下:
{
2: [1, 1.0, 1, 0, 0.5],
3: [7, 4.42, 16, 2, 5.33],
4: [2, 1.0, 2, 0, 0.5],
5: [5, 2.15, 16, 0, 3.2],
6: [8, 3.09, 16, 3, 2.67],
7: [16, 5.7, 52, 4, 7.43],
8: [3, 1.0, 4, 0, 0.5],
9: [19, 5.99, 52, 7, 5.78],
10: [6, 1.81, 16, 1, 1.6]
}
3、根据开始数字到结束数字的雹程序列分析结果,从中找到雹程最大的数字和顶峰值最大的数字,并保存在字典中。比如从2到10之间,雹程最大和顶峰值等最大的结果如下:
{
‘Len’: [19, 5.99, 52, 7, 5.78, 9],
‘Len_Ratio’: [19, 5.99, 52, 7, 5.78, 9],
‘Max’: [16, 5.7, 52, 4, 7.43, 7],
‘Ratio’: [16, 5.7, 52, 4, 7.43, 7]
}
雹程最大的数字是9,雹程:19
雹程比最大的数字是9,雹程比:5.99
顶峰值最大的数字是7,顶峰值是52,出现在第4步
倍率最大的是7,倍率为:7.43
主要实现方法:
递归调用获取每个数字的雹程序列并保存在字典中。并且,为了加快计算速度,节省时间,在计算时,遇到在雹程序列字典中已经存在的数字,则直接拼接。
比如:4的雹程是:[4,2,1],则在计算8的雹程时,只需要计算第1步,得到[8],而在第1步后得到4后,则直接将4的雹程拼接过去,即:[8] + [4,2,1] = [8,4,2,1]。
其它2个小功能:
1、统计代码运行时间:由于主要功能是通过递归调用实现的,无法通过函数装饰器来统计递归函数的运行时间,只能通过上下文管理器csTimeit()实现该功能。
2、在代码运行时可以查看到运行进度:通过tqdm库中trange函数实现。

2.2 源码

# Encoding: utf-8
# Author: 思必得
# Date: 2023/8/31 21:22
# Project name: PythonFiles
# IDE: PyCharm
# File name: bingbao
# Python required version:3.10.2
# 模块说明:
"""
"""
# 导入模块:
from mdTools import csTimeit
from mdLog import csLog
from icecream import ic
from tqdm import trange
from math import log2
# 更新日志:
"""
1、2023/8/31:
    a、创建文件
"""
# 待修改:
"""
"""
log = csLog()


class BingBao:
    def __init__(self, begin=2, end=28):
        self.begin = begin  # 开始数字
        self.end = end  # 结束数字
        self.dct_result = {}  # 冰雹序列结果字典,key:数字,value:[雹程序列]
        self.dct_assay = {}  # 冰雹序列结果分析字典,key:数字,value:[雹程,雹程比,顶峰值,顶峰值位置,倍率]
        self.dct_max = {'Len': [0, 0, 0, 0, 0, 0], 'Len_Ratio': [0, 0, 0, 0, 0, 0], 'Max': [0, 0, 0, 0, 0, 0], 'Ratio': [0, 0, 0, 0, 0, 0]}  # 冰雹序列结果分析后,雹程最大、顶峰值、倍率最大的字典。
        self.raw = 0

    def bingbao(self, num=9, lst=None):
        """
        计算某个数字的冰雹序列
        @param num: 要计算冰雹序列的数字
        @param lst: 冰雹序列
        @return: 该数字的冰雹序列
        """
        if lst is None:
            lst = [num]
            self.raw = num
        if num == 1:  # 掉落到1
            self.dct_result[self.raw] = lst
            return lst
        elif num % 2 == 0:  # 掉落到偶数
            _temp = num // 2
            if _temp in self.dct_result:
                lst.extend(self.dct_result[_temp])
                return self.bingbao(num=1, lst=lst)
            else:
                lst.append(_temp)
                return self.bingbao(num=_temp, lst=lst)
        elif num % 2 == 1:  # 掉落到奇数
            _temp = num * 3 + 1
            if _temp in self.dct_result:
                lst.extend(self.dct_result[_temp])
                return self.bingbao(num=1, lst=lst)
            else:
                lst.append(_temp)
                return self.bingbao(num=_temp, lst=lst)

    def main(self):
        for i in trange(self.begin, self.end):  # 显示代码运行进度
            self.bingbao(num=i)
        for k, v in self.dct_result.items():
            V = v[1:]
            Len = len(V)
            Len_Ratio = round(Len/log2(k), 2)
            Max = max(V)
            Index = V.index(Max)
            Ratio = round(Max / k, 2)
            self.dct_assay[k] = [Len, Len_Ratio, Max, Index, Ratio]
        for k, v in self.dct_assay.items():
            if v[0] > self.dct_max['Len'][0]:
                self.dct_max['Len'] = v + [k]
            if v[1] > self.dct_max['Len_Ratio'][1]:
                self.dct_max['Len_Ratio'] = v + [k]
            if v[2] > self.dct_max['Max'][2]:
                self.dct_max['Max'] = v + [k]
            if v[4] > self.dct_max['Ratio'][4]:
                self.dct_max['Ratio'] = v + [k]

    def get_result(self):
        return self.dct_result

    def get_assay(self):
        return self.dct_assay

    def get_max(self):
        return self.dct_max


if __name__ == '__main__':
    with csTimeit():  # 计算代码运行时间
        bb = BingBao(2, 11)
        bb.main()
        ic(bb.get_result())
        ic(bb.get_assay())
        ic(bb.get_max())

输出:

100%|██████████| 9/9 [00:00 20:58:23|> bb.get_result(): {
2: [2, 1],
3: [3, 10, 5, 16, 8, 4, 2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
6: [6, 3, 10, 5, 16, 8, 4, 2, 1],
7: [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
9: [9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1],
10: [10, 5, 16, 8, 4, 2, 1]
}
20:58:23|> bb.get_assay(): {
2: [1, 1.0, 1, 0, 0.5],
3: [7, 4.42, 16, 2, 5.33],
4: [2, 1.0, 2, 0, 0.5],
5: [5, 2.15, 16, 0, 3.2],
6: [8, 3.09, 16, 3, 2.67],
7: [16, 5.7, 52, 4, 7.43],
8: [3, 1.0, 4, 0, 0.5],
9: [19, 5.99, 52, 7, 5.78],
10: [6, 1.81, 16, 1, 1.6]
}
20:58:24|> bb.get_max(): {
‘Len’: [19, 5.99, 52, 7, 5.78, 9],
‘Len_Ratio’: [19, 5.99, 52, 7, 5.78, 9],
‘Max’: [16, 5.7, 52, 4, 7.43, 7],
‘Ratio’: [16, 5.7, 52, 4, 7.43, 7]
}
2023-09-01 20:58:24,008 - INFO - mdTools.py - exit - 338: 运行结束,目标代码片段总耗时:0.0660 秒

2.3 算大数

开始值:10
结束值:1000_0000(千万)
注意:
1、数字雹程序列和每个数字的分析结果这两个字典不要输出,否则会占用大量的时间,而且输出也不全。只需要最后的雹程最大和顶峰值最大等即可。
2、由于用到了递归调用,而在Python中,递归调用是有默认最大深度的,默认最大递归深度是1000,超过这个会报错。所以我们需要修改一下,添加以下代码:

import sys
sys.setrecursionlimit(10000)  # 设置最大递归深度为10000

开始运行吧:

if __name__ == '__main__':
    with csTimeit():  # 计算代码运行时间
        bb = BingBao(10, 1000_0000)
        bb.main()
        # ic(bb.get_result())
        # ic(bb.get_assay())
        ic(bb.get_max())

输出:

100%|██████████| 9999990/9999990 [01:04<00:00, 154736.91it/s]
21:09:01|> bb.get_max(): {
‘Len’: [685, 29.78, 159424614880, 189, 18977.97, 8400511],
‘Len_Ratio’: [685, 29.78, 159424614880, 189, 18977.97, 8400511],
‘Max’: [576, 25.42, 60342610919632, 162, 9099150.81, 6631675],
‘Ratio’: [576, 25.42, 60342610919632, 162, 9099150.81, 6631675]
}
2023-09-01 21:09:01,875 - INFO - mdTools.py - exit - 338: 运行结束,目标代码片段总耗时:107.9190 秒

可以看到,在1千万数字以内:
雹程最大的数字是8400511,雹程为685步!
雹程比最大的数字是8400511,雹程比为29.78
顶峰值最大的数字是6631675,其顶峰值为:60342610919632(60多万亿!)。
倍率最大的数字也是6631675,其倍率为:9099150.81(90多万倍!)。

你可能感兴趣的:(Python小项目,python,开发语言,Python小项目)