文字の配列を文字列にする

Ruby と違って Smalltalk の文字列(a String)は要素がすべて文字(a Character)の特殊なコレクションです。ただ、文字のみからなる配列を文字列にしたいとき、

#($a $b $c) asString

だと、

"=> '#($a $b $c)' "

というように、元の配列の文字列表現になってしまうので駄目じゃん…と思っていたのですね。でも、#as: というのがありました。

#($a $b $c) as: String
"=> 'abc' "

ぐっど、ぐっど。


追記
もちろん、正統派ならはじめからこうします。

String withAll: #($a $b $c)    " => 'abc' "

あくまで、注目している文字のコレクションをレシーバにしたいとき、の話です。

n 番目の組み合わせ

id:sumim:20041206#p1 の続き。c. も比較的、簡単そうなのでトライしてみました。

target chars
target _ 99999999999999999. chars _ '!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'. ^ chars combinations: 16 atATimeAt: target + 1
=> "%'[03569FfLoUXy

定義は、

SequenceableCollection >> combinations: digits atATimeAt: target
   | sum cursor numOfCombs resultDigit |
   sum _ 0.
   cursor _ 1.
   numOfCombs _ (self size - cursor) take: digits - 1.
   [(sum + numOfCombs) < target] whileTrue: [
      sum _ sum + numOfCombs.
      cursor _ cursor + 1.
      numOfCombs _ (self size - cursor) take: digits - 1].
   resultDigit _ self species with: (self at: cursor).
   ^ resultDigit, ((digits - 1) > 0 
      ifTrue: [(self allButFirst: cursor) combinations: digits - 1 atATimeAt: target - sum]
      ifFalse: [self species new])

Integer >> #take: は、レシーバが n、パラメータが r のときの組み合わせの数 n C r を返すメソッドです。

続: n 番目の組み合わせ

ループを排除して、心持ち、すっきりとさせてみました。心持ち。w

SequenceableCollection >> combinations: digits atATimeAt: target
   | size sum ranges cursor digit |
   size _ self size.
   (size < digits or: [digits < 1]) ifTrue: [^ self species new].
   ranges _ OrderedCollection new.
   (size - 1 to: digits - 1 by: -1) 
      inject: 0 into:  [: lastIndex : n | ranges add: lastIndex + (n take: digits - 1)].
   cursor _ ranges findFirst: [: lastIndex | lastIndex >= target].
   sum _ cursor > 1 ifTrue: [ranges at: cursor - 1] ifFalse: [0].
   digit _ self species with: (self at: cursor).
   ^ digit, ((self allButFirst: cursor) combinations: digits - 1 atATimeAt: target - sum)

Shiro さんの組み合わせの数版を Smalltalk で

組み合わせの数を使うとこんな感じ?

(let loop ((chars (string->list *chars*))
           (str '())
           (num *value*))
   (if (= (length str) 16) (list->string (reverse str))
       (let ((x (c (- (length chars) 1) (- 15 (length str)))))
         (if (> x num) (loop (cdr chars) (cons (car chars) str) num)
             (loop (cdr chars) str (- num x))))))

=> "\"%'[03569FfLoUXy"

これを、意味を持つ範囲で(reverse とかは真似てもしかたがないので…)できるだけ忠実に Squeak システムの Smalltalk 言語に訳すとこんなかんじ。

SequenceableCollection >> loopWith: str with: num
   | chars x |
   chars _ self.
   str size = 16 ifTrue: [^ str as: String].
   x _ chars size - 1 take: 15 - str size.
   ^ x > num
      ifTrue: [chars allButFirst loopWith: (str copyWith: chars first) with: num]
      ifFalse: [chars allButFirst loopWith: str with: num - x]
value chars
value _ 99999999999999999. chars _ '!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'. ^ chars probCLoopWith: #() andWith: value => "%'[03569FfLoUXy

かないません。w orz

続々: n 番目の組み合わせ

Shiro さん版を受けて、SequenceableCollection >> #combinations:atATimeAt: を改良してみました。

SequenceableCollection >> combinations: digits atATimeAt: target
   | size lastIndex |
   size _ self size.
   (size < digits or: [digits < 1]) ifTrue: [^ self species new].
   lastIndex _ size - 1 take: digits - 1.
   ^ lastIndex >= target
      ifTrue: [(self first: 1), (self allButFirst combinations: digits - 1 atATimeAt: target)]
      ifFalse: [self allButFirst combinations: digits atATimeAt: target - lastIndex]

最初っからこう書ければ、かっこいいんですけどねぇ…。orz