「年子平方数の最初の21個を列挙する」を Squeak Smalltalk で

2004年8月号 Cマガ電脳クラブ 第161回 年子平方数
┌─────────────────────────────┐
│ 偶数桁の平方数(ある数を2乗してできる数)で、それぞれ同じ│
│桁数になるように左右半分に割ったとき、その2つの数が連続し│
│た2数になっているものを、「年子(としご)平方数」と呼ぶこと│
│にする。たとえば、                    │
│ 8281(=91^2)                      │
│では81と82が連続した2数となっている。ただし、右半分と│
│左半分、どちらが大きくてもかまわない。ここでは正の整数のみ│
│を扱い、すなおに10進法で考える。            │
│ この「年子平方数」をほかにも見つけてほしいのだが、たくさ│
│んあるので、小さいほうから順に(上記の例を含めて)21番目ま│
│でをお答えいただきたい。                 │
└─────────────────────────────┘

via 年子平方数の最初の21個を列挙する - ドレッシングのような
| nn scale results add isSquare print |
nn := scale := 10.
results := OrderedCollection new.
World findATranscript: nil.
add := [:result | (results add: result; size) = 21 ifTrue: [^results] ifFalse: [result]].
isSquare := [:val | val sqrt asInteger squared = val].
print := [:val | Transcript cr; show: val. add value: val].

[   (isSquare value: nn) ifTrue: [print value: nn].
    scale - (scale / 10) - 1 timesRepeat: [
        nn := nn + 2.
        (isSquare value: nn) ifTrue: [print value: nn].
        nn := nn + scale - 1.
        (isSquare value: nn) ifTrue: [print value: nn]].
    scale := scale * 10.
    nn := (scale + 1) * (scale / 10) - 1
] repeat
8281
183184
328329
528529
715716
60996100
82428241
98029801
1322413225
4049540496
106755106756
453288453289
538277538276
998002998001
20661152066116
29752082975209
2214532822145329
2802768328027684
7783702677837025
9998000299980001
110213248110213249


空気を読まずにネタバレしてしまうと、処理系によっては(Smalltalk 処理系であっても)桁あふれに要注意みたいです。

たとえば Cincom SmalltalkVisualWorks)の場合 a Float が単精度なので、val sqrt のところで a Float を返す Number>>#sqrtをコールしてしまわないように、val asDouble sqrt として Double>>#sqrt を呼ぶようにしないと、ある数を超えると永遠に判定に失敗し続けるハメになります。ただし、Squeak Smalltalk の場合は浮動小数点数が倍精度なので、今回の範囲では問題はないようです。


Ruby に直訳ぎみに書き直したものも。

begin
  n = scale = 10
  results = []
  add = Proc.new{ |result| results << result; results.size == 16 ? return results : result }
  is_square = proc{ |val| Math.sqrt(val).to_i ** 2 == val }
  print = proc{ |val| puts val; add[val] }

  loop{
    print[n] if is_square[n]
    (scale - (scale / 10) - 1).times{
      n += 2
      print[n] if is_square[n]
      n += scale - 1
      print[n] if is_square[n]
    }
    scale *= 10
    n = (scale + 1) * (scale / 10) - 1
  }
rescue
end


Smalltalk と同じ方法で終了させようとすると LocalJumpError でしかられてしまうので rescue しています。^^; この手の脱出をしたいときは Ruby ではどう書いたらいいのでしょうか?


…ということで、コメント欄で id:mrkn さんに教えていただいた catch/throw 版も。

n = scale = 10
results = []
add = proc{ |result| results << result; results.size == 21 ? throw(:exit, results) : result }
is_square = proc{ |val| Math.sqrt(val).to_i ** 2 == val }
print = proc{ |val| puts val; add[val] }

catch(:exit){
  loop{
    print[n] if is_square[n]
    (scale - (scale / 10) - 1).times{
      n += 2
      print[n] if is_square[n]
      n += scale - 1
      print[n] if is_square[n]
    }
    scale *= 10
    n = (scale + 1) * (scale / 10) - 1
  }
}

さらに id:ujihisa さんにヒントをもらって調べ直したところ、Ruby1.9 と proc の組み合わせ(つまり Ruby1.8 を使ったり、Ruby1.9 でも lambda による Proc 生成ではNG)で期待通りの動作をさせられることが分かりました。ありがとうございます。

n = scale = 10
results = []
add = proc{ |result| results << result; results.size == 21 ? break : result }
is_square = proc{ |val| Math.sqrt(val).to_i ** 2 == val }
print = proc{ |val| puts val; add[val] }


loop{
  print[n] if is_square[n]
  (scale - (scale / 10) - 1).times{
    n += 2
    print[n] if is_square[n]
    n += scale - 1
    print[n] if is_square[n]
  }
  scale *= 10
  n = (scale + 1) * (scale / 10) - 1
}