はじめに
コラッツ予想のテレビ番組を見てプログラムの教材に丁度良いと思ったので計算するプログラムを作ってみました。
証明ではありません。
工夫する事で速度向上が出来そうでしたのでいくつかのデータ構造を使って比較してみました。
公開当時は大分酷いコードを載せていましたが更新しました。
環境
Windows 11 Pro
Python 3.11.4 (tags/v3.11.4:d2340ef, Jun 7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)]
コード
import sys
import argparse
import pickle
import timeit
import gc
import numpy as np
import Utility as U
import Decorator as D
class Collatz():
def __init__(self, start, end):
self.start = start
self.end = end
def isOk(self, n, done):
return n <= done or n == 1
def calc(self, n, doPrint=False):
m = n - 1
if doPrint:
print(f"\ncalc : {n}", end="")
while not self.isOk(n, m):
q, r = divmod(n, 2)
n = q if r == 0 else 3 * n + 1
if doPrint:
print(f" -> {n}", end="")
if doPrint:
print("")
@D.printStartEndExecuteTime
def search(self, doPrint=False):
print(f"Collatz conjecture: {self.start} -> {self.end}")
l = U.getDigits(self.end)
for i in range(self.start, self.end + 1):
print(f"\r\x1b[1M{i:0{l}}", end="")
self.calc(i, doPrint)
print(" done")
def print(self):
print(f"total size : {sys.getsizeof(self)}")
print()
class CollatzSet(Collatz):
def __init__(self, start, end, name="dumpSet.dat", load=False):
super().__init__(start, end)
self.max = start - 1
self.name = name
self.checked = set()
self.work = set()
if load:
self.load()
def isOk(self, n):
return n <= self.max or n in self.checked or n == 1
def addWork(self, n):
self.work.add(n)
def calc(self, n, doPrint=False):
m = n
if doPrint:
print(f"\ncalc : {m}", end="")
while not self.isOk(m):
q, r = divmod(m, 2)
m = q if r == 0 else 3 * m + 1
if m > n:
self.addWork(m)
if doPrint:
print(f" -> {m}", end="")
if doPrint:
print(f"\n{sorted(self.work)}")
print(f"{sorted(self.checked)}")
def update(self, n):
if n <= self.max:
return
self.max = n
self.checked.difference_update([self.max])
self.checked.update(self.work)
self.work.clear()
@D.printStartEndExecuteTime
def search(self, doPrint=False):
print(f"Collatz conjecture: {self.start} -> {self.end}")
l = U.getDigits(self.end)
for i in range(self.start, self.end + 1):
print(f"\r\x1b[1M{i:0{l}}", end="")
self.calc(i, doPrint)
self.update(i)
print(" done")
self.dump()
def print(self, printChecked=False):
print(f"total size : {sys.getsizeof(self)+sys.getsizeof(self.checked)}")
print(f"checked len : {len(self.checked)}")
print(f"checked size : {sys.getsizeof(self.checked)}")
if printChecked:
print(sorted(self.checked))
print()
def dump(self):
obj = {
"max": self.max,
"checked": self.checked,
}
with open(self.name, "bw") as file:
pickle.dump(obj, file)
def load(self):
with open(self.name, "br") as file:
obj = pickle.load(file)
self.max = obj["max"]
self.checked = obj["checked"]
class CollatzList(CollatzSet):
def __init__(self, start, end, name="dumpList.dat", load=False):
super().__init__(start, end, name, load)
self.checked = []
self.work = []
if load:
self.load()
def addWork(self, n):
self.work.append(n)
def update(self, n):
if n <= self.max:
return
self.max = n
if len(self.work) > 0:
work = [x for x in self.checked if x > self.max and x not in self.work]
self.checked = work + self.work
self.work.clear()
class CollatzNp(CollatzSet):
def __init__(self, start, end, name="dumpNp.dat", load=False):
super().__init__(start, end, name, load)
self.checked = np.array([], np.int32)
self.work = np.array([], np.int32)
if load:
self.load()
def addWork(self, n):
self.work = np.append(self.work, n)
def update(self, n):
if n <= self.max:
return
self.max = n
if len(self.work) > 0:
self.checked = np.union1d(self.checked[self.checked > self.max], self.work)
self.work = np.array([], np.int32)
def argParser():
parser = argparse.ArgumentParser()
parser.add_argument("end", type=int, help="End value.")
parser.add_argument("-s", "--start", type=int, default=1, help="Start value.")
# parser.add_argument("-i", "--name", default="dump.dat", help="file name.")
parser.add_argument("-l", "--load", action="store_true", help="load.")
parser.add_argument("-p", "--print", action="store_true", help="print.")
return parser.parse_args()
if __name__ == "__main__":
args = argParser()
clz = Collatz(args.start, args.end)
clz.search(args.print)
clz.print()
clz = CollatzSet(args.start, args.end, load=args.load)
clz.search(args.print)
clz.print(False)
clz = CollatzList(args.start, args.end, load=args.load)
clz.search(args.print)
clz.print(False)
clz = CollatzNp(args.start, args.end, load=args.load)
clz.search(args.print)
clz.print(False)
def getDigits(number):
return len(str(number))
import time
import functools
import gc
import inspect
import TimeUtility as TU
import Utility as U
def printStartEndExecuteTime(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
gc.collect()
gc.disable()
print(f"{TU.getNowTime()} - Start {func.__name__}")
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
diff = end - start
print(f"{TU.getNowTime()} - End {func.__name__} {TU.secToTime(diff)}({diff:.3f})")
gc.enable()
return result
return wrapper
import time
import datetime
# 現在時刻を取得
def getNowTime():
return datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]
# return time.strftime("%H:%M:%S")
実行結果例
> py .\collatzConjecture.py 10 -p
01:34:53.485 - Start search
Collatz: 1 -> 10
01
calc : 1
02
calc : 2 -> 1
03
calc : 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2
04
calc : 4 -> 2
05
calc : 5 -> 16 -> 8 -> 4
06
calc : 6 -> 3
07
calc : 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5
08
calc : 8 -> 4
09
calc : 9 -> 28 -> 14 -> 7
10
calc : 10 -> 5
done
01:34:53.486 - End search 00:00:00.002(0.002)
total size : 56
01:34:53.491 - Start search
CollatzSet: 1 -> 10
01
calc : 1
[]
[]
02
calc : 2 -> 1
[]
[]
03
calc : 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2
[4, 5, 8, 10, 16]
[]
04
calc : 4
[]
[4, 5, 8, 10, 16]
05
calc : 5
[]
[5, 8, 10, 16]
06
calc : 6 -> 3
[]
[8, 10, 16]
07
calc : 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10
[10, 11, 13, 17, 20, 22, 26, 34, 40, 52]
[8, 10, 16]
08
calc : 8
[]
[8, 10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
09
calc : 9 -> 28 -> 14 -> 7
[14, 28]
[10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
10
calc : 10
[]
[10, 11, 13, 14, 16, 17, 20, 22, 26, 28, 34, 40, 52]
done
01:34:53.494 - End search 00:00:00.002(0.002)
total size : 784
checked len : 12
checked size : 728
01:34:53.499 - Start search
CollatzList: 1 -> 10
01
calc : 1
[]
[]
02
calc : 2 -> 1
[]
[]
03
calc : 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2
[4, 5, 8, 10, 16]
[]
04
calc : 4
[]
[4, 5, 8, 10, 16]
05
calc : 5
[]
[4, 5, 8, 10, 16]
06
calc : 6 -> 3
[]
[4, 5, 8, 10, 16]
07
calc : 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10
[10, 11, 13, 17, 20, 22, 26, 34, 40, 52]
[4, 5, 8, 10, 16]
08
calc : 8
[]
[8, 10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
09
calc : 9 -> 28 -> 14 -> 7
[14, 28]
[8, 10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
10
calc : 10
[]
[10, 11, 13, 14, 16, 17, 20, 22, 26, 28, 34, 40, 52]
done
01:34:53.503 - End search 00:00:00.003(0.003)
total size : 216
checked len : 13
checked size : 160
01:34:53.510 - Start search
CollatzNp: 1 -> 10
01
calc : 1
[]
[]
02
calc : 2 -> 1
[]
[]
03
calc : 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2
[4, 5, 8, 10, 16]
[]
04
calc : 4
[]
[4, 5, 8, 10, 16]
05
calc : 5
[]
[4, 5, 8, 10, 16]
06
calc : 6 -> 3
[]
[4, 5, 8, 10, 16]
07
calc : 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10
[10, 11, 13, 17, 20, 22, 26, 34, 40, 52]
[4, 5, 8, 10, 16]
08
calc : 8
[]
[8, 10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
09
calc : 9 -> 28 -> 14 -> 7
[14, 28]
[8, 10, 11, 13, 16, 17, 20, 22, 26, 34, 40, 52]
10
calc : 10
[]
[10, 11, 13, 14, 16, 17, 20, 22, 26, 28, 34, 40, 52]
done
01:34:53.514 - End search 00:00:00.004(0.004)
total size : 220
checked len : 13
checked size : 164
> py .\collatzConjecture.py 100000
01:38:24.283 - Start search
Collatz: 1 -> 100000
100000 done
01:38:27.399 - End search 00:00:03.116(3.116)
total size : 56
01:38:27.405 - Start search
CollatzSet: 1 -> 100000
100000 done
01:38:30.729 - End search 00:00:03.324(3.324)
total size : 4194576
checked len : 117212
checked size : 4194520
01:38:30.739 - Start search
CollatzList: 1 -> 100000
100000 done
01:42:42.961 - End search 00:04:12.221(252.221)
total size : 937816
checked len : 117213
checked size : 937760
01:42:42.972 - Start search
CollatzNp: 1 -> 100000
100000 done
01:43:09.201 - End search 00:00:26.229(26.229)
total size : 469020
checked len : 117213
checked size : 468964
> py .\collatzConjecture.py 200000 -l
01:48:33.760 - Start search
Collatz: 1 -> 200000
200000 done
01:48:39.990 - End search 00:00:06.230(6.230)
total size : 56
01:48:40.004 - Start search
CollatzSet: 1 -> 200000
200000 done
01:48:46.262 - End search 00:00:06.258(6.258)
total size : 8388880
checked len : 233729
checked size : 8388824
01:48:46.280 - Start search
CollatzList: 1 -> 200000
200000 done
02:02:08.998 - End search 00:13:22.719(802.719)
total size : 1869976
checked len : 233733
checked size : 1869920
02:02:09.014 - Start search
CollatzNp: 1 -> 200000
200000 done
02:03:53.356 - End search 00:01:44.342(104.342)
total size : 1870032
checked len : 233733
checked size : 1869976
> py .\collatzConjecture.py 200000
02:06:19.541 - Start search
Collatz: 1 -> 200000
200000 done
02:06:26.120 - End search 00:00:06.579(6.579)
total size : 56
02:06:26.126 - Start search
CollatzSet: 1 -> 200000
200000 done
02:06:33.188 - End search 00:00:07.062(7.062)
total size : 8388880
checked len : 233729
checked size : 8388824
02:06:33.200 - Start search
CollatzList: 1 -> 200000
200000 done
02:25:20.663 - End search 00:18:47.463(1127.463)
total size : 1869976
checked len : 233733
checked size : 1869920
02:25:20.676 - Start search
CollatzNp: 1 -> 200000
200000 done
02:27:27.919 - End search 00:02:07.242(127.242)
total size : 1870032
checked len : 233733
checked size : 1869976
解説
コラッツ予想には、
- xが1になる場合、その計算過程で出現した値は全て1になる
- xまで証明した場合、計算過程でxより小さい数が出現したらその値は1になる
という性質があります。
そこでその判定をするために計算過程のデータを保存しています。
Collatz()
は、単純に計算していくだけのクラスです。
計算自体が単純なので結局一番速いです。
CollatzSet()
は、計算過程のデータをset
に保存して参照するクラスです。
データを検索・更新する処理が入ったために速度が少し悪化しています。
そして、メモリ使用量が跳ね上がってしまいます。
CollatzList()
は、比較のために計算過程のデータをlist
に保存するクラスです。
実行時間が遅すぎて役に立たちませんが、set
よりメモリ使用量は抑えられます。
CollatzNp()
は、NumPyのndarray
に保存するクラスです。
list
と比べると、速度・メモリ使用量に大幅な改善が見られます。
ただ、set
にも劣ってしまいます。
これらのプログラムの欠点は、
- xが1にならない場合の判定が無く無限ループに陥ってしまう
という事です。
もっとも、1にならない値を見つけられたらコラッツ予想を証明出来てしまうのですが。