Squeak4.1に追加されたジェネレーターを使って、掛け合わせると36になる3つの数の組を列挙する


この「三人の子の年齢当てパズル」に絡めて、

【純粋な論理パズルです】A「私の3人 の子供の年齢を当ててみて」B「ヒントは?」A「年齢の積は36」B「もう少しヒントを」A「年齢の和はあなたの年齢と同じ」B「、、、じゃあ1つ質問。 年齢が一番上の子の名前は?」A「たかゆき」B「OK、それで分かった」 さて、3人の子供の年齢は?

Twitter / 池田洋介


掛け合わせると36になる3つの数の組を列挙する(つまり、36を3つの因数に分解する)機能が欲しくなったのですが、簡潔に書けないかとつぶやいたところ、@nishioさんがこんな RT をくれたので、

[(x,y,z)for x in range(37)for y in range(37)for z in range(37)if x*y*z==36] RT @sumim かけると36になる三つの数の組み合わせをすべて列挙するコードを140文字以下で書きたいが書けない。

Twitter / nishio hirokazu


内包表現までは無理としても、Squeak4.1 にはジェネレーターがあったような…と書いてみたのがこちら。

| gen triples |
gen := Generator on: [:g |
   (1 to: 36) asDigitsToPower: 3 do:  [:digits |
      (digits inject: 1 into: #*) = 36 ifTrue: [g value: digits copy]]].

triples := Set new.
gen do: [:triple | triples add: triple sort].
^triples
=> a Set(
   #(1 1 36)
   #(1 6 6)
   #(1 2 18)
   #(3 3 4)
   #(2 3 6)
   #(1 3 12)
   #(1 4 9)
   #(2 2 9))


Python のジェネレーターや内包表現のようにスマートではないのですが、まあ、この手の処理を書くときには便利に使えそうです。


で、件のパズルのほうは(以下はネタバレ注意)、これら因数の組をその和でグループ分けしてみれば謎は解けるわけです。Squeak Smalltalk であれば、#groupBy:having: を使うのがいいでしょう。

| gen triples |
gen := Generator on: [:g |
   (1 to: 36) asDigitsToPower: 3 do:  [:digits |
      (digits inject: 1 into: #*) = 36 ifTrue: [g value: digits copy]]].

triples := Set new.
gen do: [:triple | triples add: triple sort].

^triples groupBy: [:tri | tri sum] having: [:grp | grp size > 1]
"=> a PluggableDictionary(13->an OrderedCollection(#(1 6 6) #(2 2 9)) ) "


Python で書いてつぶやいたのはこちら。

@nishio なるほど。def splitnum3(n): return set([tuple(sorted([x,y,z])) for x in range(n+1) for y in range(n+1) for z in range(n+1) if x*y*z==n])

Twitter / sumim

@nishio 完成。 [(k, map(lambda x : x[1:], list(vs))) for k, vs in groupby(sorted([(sum(t),) + t for t in splitnum3(36)]), key=lambda x : x[0])]

Twitter / sumim


これに対する @nishoさんの手直し版がこちら。

@sumim r=range(37);[(k,[x[1]for x in vs])for k,vs in groupby([(x+y+z,(x,y,z))for x in r for y in r for z in r if x*y*z==36],lambda x:x[0])]

Twitter / nishio hirokazu