2004年8月号 Cマガ電脳クラブ 第161回 年子平方数
via 年子平方数の最初の21個を列挙する - ドレッシングのような
┌─────────────────────────────┐
│ 偶数桁の平方数(ある数を2乗してできる数)で、それぞれ同じ│
│桁数になるように左右半分に割ったとき、その2つの数が連続し│
│た2数になっているものを、「年子(としご)平方数」と呼ぶこと│
│にする。たとえば、 │
│ 8281(=91^2) │
│では81と82が連続した2数となっている。ただし、右半分と│
│左半分、どちらが大きくてもかまわない。ここでは正の整数のみ│
│を扱い、すなおに10進法で考える。 │
│ この「年子平方数」をほかにも見つけてほしいのだが、たくさ│
│んあるので、小さいほうから順に(上記の例を含めて)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 Smalltalk(VisualWorks)の場合 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 }