Hamming number

Hamming numbers - Rosetta Code から
素因数分解した形が 2^i 3^j 5^k になる数をハミング数というらしい。ただし i, j, k >= 0

Haskell のコードを引用する。

Haskell
hamming = 1 : map (2*) hamming `merge` map (3*) hamming `merge` map (5*) hamming
     where merge (x:xs) (y:ys)
            | x < y = x : xs `merge` (y:ys)
            | x > y = y : (x:xs) `merge` ys
            | otherwise = x : xs `merge` ys
 
main = do
    print $ take 20 hamming
    print $ hamming !! 1690
    print $ hamming !! 999999


多分 Regular number - Wikipediaアルゴリズムをそのまま実装するとこうなる。*1

  • ハミング数は 1 から始まる
  • 残りの数はハミング数を 2倍、3倍、5倍したものである
  • ハミング数の並びは 2倍、3倍、5倍した数をマージしたものである


今回も Groovy に直訳しようとしたが再帰できるリストが実装できなかった。
残念ながら理解が足りていないのだろう。
イメージとしては次のようになる。

      5
      4
      3
      2
      1
      -->| output
merge |<--
      2,3,5
      4,6,10
      6,9,15

今までみた無限リストと違い、ぐるぐる回っている。
output を 2倍、3倍、5倍して merge してまた output する。

take 3 [1..]                          -- ずっと続くイメージ
let xs = 1:map (+1) xs in take 3 xs   -- 回っているイメージ


直訳できなかったので『C言語による最新アルゴリズム事典』を Groovy に書き換えたものを

def hamming(N) {
  def q = []
  def j2 = j3 = j5 = 0
  def x2 = x3 = x5 = 1G
  for (i = 0; i < N; i++) {
    q[i] = [x2,x3,x5].min()
    while (x2 <= q[i]) x2 = 2 * q[j2++]
    while (x3 <= q[i]) x3 = 3 * q[j3++]
    while (x5 <= q[i]) x5 = 5 * q[j5++]
  }
  q
}
assert hamming(20) == [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36]
assert hamming(1691).last() == 2125764000G
assert hamming(1000000).last() == 519312780448388736089589843750000000000000000000000000000000000000000000000000000000G


Scala のコードを引用する。

Scala
class Hamming extends Iterator[BigInt] {
  import scala.collection.mutable.Queue
  val q2 = new Queue[BigInt]
  val q3 = new Queue[BigInt]
  val q5 = new Queue[BigInt]
  def enqueue(n: BigInt) = {
    q2 enqueue n * 2
    q3 enqueue n * 3
    q5 enqueue n * 5
  }
  def next = {
    val n = q2.head min q3.head min q5.head
    if (q2.head == n) q2.dequeue
    if (q3.head == n) q3.dequeue
    if (q5.head == n) q5.dequeue
    enqueue(n)
    n
  }
  def hasNext = true
  List(q2, q3, q5) foreach (_ enqueue 1)
}

q を Queue に置き換えた感じ。


Python の実装は Cyclic Iterator と書かれていた。
詳しくないので分からないが yield があれば回るリストを実装できるのかな?

*1:意訳