1 から 1000 まで続けて書いてできる整数…を Squeak Smalltalk でいろいろと
1から15まで続けて書くと123456789101112131415となる。これを1つの整数と考えると、この数は21けたで,1が8回使われている。このように、1からある整数まで続けて書いてできる整数について、次の各問いに答えよ。
灘中学校1998年2日目第1問(問題)
(1)1から100まで続けて書いてできる整数は何けたか。
(2)1から1000まで続けて書いてできる整数は何けたか。また、その整数の中に1は何回使われているか。
| stream | stream := String new writeStream. (1 to: 1000) do: [:each | stream nextPutAll: each printString]. ^ {'桁' -> stream size. '1の数' -> (stream contents occurrencesOf: $1)}
| bag | bag := Bag new. (1 to: 1000) do: [:each | bag addAll: each printString]. ^ {'桁' -> bag size. '1の数' -> (bag occurrencesOf: $1)}
| numOfDigits numOfOnes | numOfDigits := numOfOnes := 0. (1 to: 1000) do: [:each | | strOfInt | strOfInt := each printString. numOfDigits := numOfDigits + strOfInt size. numOfOnes := numOfOnes + (strOfInt occurrencesOf: $1)]. ^ {'桁' -> numOfDigits. '1の数' -> numOfOnes}
| stream numOfOnes | stream := ReadWriteStream on: String new. (1 to: 1000) do: [:each | stream nextPutAll: each printString]. numOfOnes := 0. stream reset; do: [:each | each == $1 ifTrue: [numOfOnes := numOfOnes + 1]]. ^ {'桁' -> stream size. '1の数' -> numOfOnes}
二番目の bag のが最もしっくりくる感じ。
おまけ。
^ {'桁' -> ((#(1 2 3) * 9 polynomialEval: 10) + 4). '1の数' -> (3 * 1000 / 10 + 1)}
おまけ2。
ブックマークで Haskell のワンライナーを頂いたので、これを Squeak Smalltalk に意訳。
| f q | f := [:n | n < 10 ifTrue: [{n}] ifFalse: [(f fixTemps copy value: n // 10), {n \\ 10}]]. q := (1 to: 1000) inject: #() into: [:result :each | result, (f fixTemps copy value: each)]. ^ {q size. (q select: [:each | each == 1]) size}
おまけ3。
対抗して、Squeak Smalltalk でワンライナー。
(1 to: 1000) inject: #(0 0) into: [:r :e | r + {e printString size. e printString occurrencesOf: $1}]
Haskell に直訳ぎみに意訳すると、こんな感じでしょうか。
foldl (\r e -> zipWith (+) r [length $ show e, length $ filter (=='1') $ show e]) [0,0] [1..1000]
追記。
rubyco さんに rubyco(るびこ)の日記 - 1から1000まで続けて書いてできる整数…を考える でリンクしていただきましたので、Rubyist 向けに上のワンライナーを直訳ぎみに Ruby にも意訳してみます。
(1..1000).inject([0,0]){ |(r0,r1),e| [r0 + e.to_s.size, r1 + e.to_s.count('1')] }
=> [2893, 301]
Haskell 版も無名関数の引数にパターンを使うことで、同様に書き直せますね。
foldl (\(r0,r1) e -> (r0 + (length $ show e), r1 + (length $ filter (=='1') $ show e))) (0,0) [1..1000]
逆に Haskell の zipWith 版のほうは Ruby ならこんなニュアンス?
(1..1000).inject([0,0]){ |r,e| r.zip([e.to_s.size, e.to_s.count('1')]).map{ |a,b| a+b } }
仮に Ruby にも、二つの配列の対応する要素同士の足し算の機能(仮に Array#plus )があったならば、Smalltalk とまったく同じ、かつ、#inject(#inject:into:)を用いた総和のイディオムに似た書き方ができます。例によってなんちゃって版をしつらえて書くと次のような感じ(ちなみに Smalltalk の Collection >> #+ では、ダブルディスパッチを用いたこれとは異なる実装になっています。念のため)。#+ が使えないのが痛いですね(^_^;)。
class Array def plus(other) zip(other).map{ |a,b| a+b } end end (1..1000).inject([0,0]){ |r,e| r.plus([e.to_s.size, e.to_s.count('1')]) }
追記2。
満足せる豚。眠たげなポチ。:1から1000まで続けて書いてできる整数、すげー。すばらしいですね。二つめは #count を使うともっと叙述的になっていいかも。
[*1..1000].join.count('1')
Smalltalk には #join という発想(要素の #to_s をつなげて文字列にする…)がないので、同じことをしようとすると、なんだかなーって結果に(^_^;)。
(String streamContents: [:ss | 1 to: 1000 do: [:each | ss print: each]]) occurrencesOf: $1
似たような発想でなら、次のような手で短く書けますけど、中間生成物(?)が違っちゃうし、それがなんとはなしに気持ちが悪い。答えはちゃんと出ますけれどもね。w
(1 to: 1000) asArray asString occurrencesOf: $1
=> 301
ちなみにこの方向性で桁の数を答えさせようとする場合には、こう。
(1 to: 1000) asArray asString count: [:each | each isDigit]
=> 2893