コンテンツにスキップ

0653: ハミング数のマージ

問題

素因数が 2, 3, 5 のみである正の整数をハミング数(正則数)と呼びます。 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15 と続き、7 や 14 のように他の素因数を含む数は現れません。 ハミング数を昇順に、重複なく、無限に生成するジェネレータ関数 hamming() を実装してください。

def hamming() -> Iterator[int]: ...

ハミング数は \(2^a 3^b 5^c\) の形の数ですが、指数の組 \((a, b, c)\) を単純な多重ループで列挙しても値の昇順にはならない点が本問の中心です。 この列を昇順に生成する課題は、Dijkstra が『A Discipline of Programming』で扱った古典問題として知られます。

制約

  • 最初の値は 1 で、以後、昇順かつ重複なしで生成します。
  • 上限を仮定してはいけません。値は多倍長整数のまま扱い、浮動小数点数(対数による大小比較など)を使ってはいけません(丸め誤差のため大きな値で順序を誤ります)。
  • 自然数を 1 から順に調べてハミング数かどうか判定する方式は禁止します。\(N\) 以下のハミング数は \(O(\log^3 N)\) 個しかないため、この方式では \(n\) 番目を得るのに \(n\) に対して指数的な時間がかかります。

>>> from itertools import islice
>>> list(islice(hamming(), 15))
[1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24]
>>> next(islice(hamming(), 999, None))  # 1000番目のハミング数
51200000

発展

  • 任意の有限素数集合に一般化した smooth(primes)(いわゆる \(k\)-smooth 数の列挙)を実装してください。
  • hamming() の出力自身を 2 倍、3 倍、5 倍した3本のストリームとみなし、それらを重複を除きながらマージする自己参照的な実装に書き直してください(itertools.tee が使えます)。\(n\) 番目までの生成で内部に保持される要素数を、元の実装と比較してください。

参考

  • 『Python Distilled』第6章「ジェネレータとyield文」
  • 『Python Distilled』第6章「ジェネレータの実例」
  • Regular number - Wikipedia