1 から 1000 まで続けて書いてできる整数…を Squeak Smalltalk でいろいろと

 1から15まで続けて書くと123456789101112131415となる。これを1つの整数と考えると、この数は21けたで,1が8回使われている。このように、1からある整数まで続けて書いてできる整数について、次の各問いに答えよ。
(1)1から100まで続けて書いてできる整数は何けたか。
(2)1から1000まで続けて書いてできる整数は何けたか。また、その整数の中に1は何回使われているか。

灘中学校1998年2日目第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