さて問題です。この人は何をしたいのでしょう?

2ちゃんの某 Ruby スレで、当初思ったより盛り上がっているのを見て、ちょっと趣向を変えて悪ノリ。スクリプトから、元の問題を想像してみてください。以下は、すべて同じ問題を解決するために書いたものです。

註:array などとして与えているのが、答えを得るために必要な配列(入力)です。ただ、ここで用意した例は、評価の結果(出力)とつき合わせたとき、問題に関する情報を与えにくいように、かなり特殊なものである #(1 2 3 4) をあえて選んで使っています。

array
array _ #(1 2 3 4). 1 to: array size + 1 do: [: index | (array includes: index) ifFalse: [^ index]]
array
array _ #(1 2 3 4). ^ ((1 to: array size + 1) select: [: each | (array includes: each) not]) min
array
array _ #(1 2 3 4). ^ ((1 to: array size + 1) reject: [: each | array includes: each]) min
set
set _ #(1 2 3 4) asSet. ^ ((1 to: 10000) asSortedCollection removeAll: set; yourself) first
set
set _ #(1 2 3 4) asSet. ^ ((1 to: 10000) asOrderedCollection removeAll: set; yourself) min
array
array _ #(1 2 3 4). ^ ((1 to: array size + 1) asOrderedCollection removeAllFoundIn: array; yourself) min
collection
collection _ #(1 2 3 4) asSet asSortedArray. collection keysAndValuesDo: [: key : value | key ~= value ifTrue: [^ value]] . ^ collection size + 1
array
array _ #(1 2 3 4). ^ (1 to: array size + 1) detect: [: element | (array includes: element) not]
array
array _ #(1 2 3 4). ^ ((1 to: array size + 1) difference: array) min


いろいろと考えた結果、次のが一番、したいことを表わせているかな…。

array
array _ #(1 2 3 4). ^ (1 to: 10000) findFirst: [: element | (array includes: element) not]

これを読み下すと、

1 から 10000 までの整数列の要素(element)で array に含まれ(include)ない(not)最初のものを探し(find first)なさい。

となります。問題は正確には、

与えられた、任意の 0 個以上の自然数(ただし 10000 以下で重複可)からなる array に含まれない最初(最小)の自然数を探しなさい。

ですが、まあ、このくらいの解釈変更でそれを(もちろん言うまでもなく、「すべてを“オブジェクト”とそれへの“メッセージ送信”で表わす」というドグマに従って)書き下すだけで答えが求められるなら、それは許容範囲というものでしょう。

くしくも、“(いろいろあるかもしれないけど、基本は)まず”と前置きをしている 928 さん と(SmalltalkRuby という違いをも越えて)まったく同じコードになったのはいろいろな意味で興味深いですね。

もちろん、10000 と決め打ちにはせずに、

array
array _ #(1 2 3 4). ^ (1 to: array size + 1) findFirst: [: element | (array includes: element) not]

とも書けます。ただ、array size + 1 は、一見、賢そうなことをしているようで、実際は、これ以上の数ならいくつでもよいため(効率も含めて)意味はなく、むしろ意図を伝わりにくくしてしまっているぶん邪魔。 なので、10000 のままのほうが実はよかったりするかもしれません。要素の上限についてもついでに記述できているわけだし。

もっとも 10000 という数自体にも(出題側の都合という以上の)さしたる意味はないので、

array
array _ #(1 2 3 4). ^ (1 to: Float infinity) findFirst: [: element | (array includes: element) not]

というふうに書くことができます。ただし正常に機能させるためには、Interval >> #size に、たとえばこんなふうな細工、

Interval >> size
  | range |
  range _ stop - start.
  step < 0
    ifTrue: [start < stop
        ifTrue: [^ 0]
        ifFalse: [range isInfinite 
          ifTrue: [^ range negated] ifFalse: [^ range // step + 1]]]
    ifFalse: [stop < start
        ifTrue: [^ 0]
        ifFalse: [range isInfinite 
          ifTrue: [^ range] ifFalse: [^ range // step + 1]]]

を施して、an Interval の start か stop の一方に Float infinity を許容するようにしておく必要があります。また、SmallInteger maxVal が十分大きな数であることを知っている人を読み手として想定できるならば、こちらは細工なしで、

array
array _ #(1 2 3 4). ^ (1 to: SmallInteger maxVal) findFirst: [: element | (array includes: element) not]

でもよさそうです。


なお、この気持ちを Haskell で表わすと、

[ x | x<-[1..], notElem x [1,2,3,4] ] !! 0

てな感じでしょうか。List.hs の (\\) を使えば、

([1..] \\ [1,2,3,4]) !! 0

で、もはや、何も考えずに状況を書きくだせばおしまいって感じ。 Smalltalk で同じことをいろいろな切り口で表わせる(普段はこの冗長性こそがアドバンテージだと思っている)ことが、なんだか気恥ずかしく思えてきて不思議です。SmalltalkHaskell の“いいとこどり”が理論的に可能だと分かれば、また違うんでしょうけどね。