Ruby との配列操作の比較


die さんの 経由で、高林さんの いやなブログ 発「配列操作の比較表: Ruby, Python, JavaScript, Perl, C++」をうけて。


似たような文法の言語間で混同しないように整理するのが、そもそもの目的のようなので、Smalltalk がごとき変態文法はお呼びでない…とも思いましたが、自らの Ruby 学習と、Squeak システムにおける Smalltalk を使った配列操作のご紹介を兼ねて項目を埋めてみました。


関連:Ruby との文字列操作の比較

Ruby
            
(Array)
SqueakSmalltalk
            
(Array)
SqueakSmalltalk
            
(OrderedCollection)

備 考
a = [1, 2, 3] a := {1. 2. 3} "N/A" c := {1. 2. 3} asOrderedCollection
      c := OrderedCollection withAll: {1. 2. 3}
  a := #(1 2 3) "N/A" "要素はリテラル限定"
  a := Array
        with: 1
        with: 2
        with: 3
c := OrderedColletion
        with: 1
        with: 2
        with: 3
"要素6つまで対応可"
a.length a size c size  
a.empty? a isEmpty c isEmpty  
a.push(x) "N/A" c add: x "非破壊的に" a copyWith: x
a.unshift(x) "N/A" c addFirst: x "非破壊的に" a copyWithFirst: x
a.pop "N/A" c removeLast "非破壊的に" a allButLast
a.shift "N/A" c removeFirst "非破壊的に" a allButFirst
a.concat(b) "N/A" c addAll: d "非破壊的に" a, b "カンマがメソッド名!"
a.clear "N/A" "N/A" "非破壊的に" a copyEmpty. c copyEmpty
a.insert(i, x) "N/A" c add: x beforeIndex: i  
a.delete(x) "N/A" c remove: x  
a.delete_at(i) "N/A" c removeAt: i  
a.nitems {|e|e==x} a count: [:e|e=x] c count: [:e|e=x]  
a.include?(x) a includes: x c includes: x  
a.index(x) a indexOf: x c indexOf: x  
a.first a first c first "second、third、...、ninth も"
a[0] a at: 1 c at: 1 "開始インデックスは 0 でなく 1 "
a.last a last c last "真ん中抽出の middle も"
a[-1] a atWrap: 0 c atWrap: 0  
a.slice(i, l); a[i,l] "N/A" "N/A" c copyFrom: i to: i+l-1 "a でも可"
a.slice(i..j); a[i..j] a copyFrom: i to: j c copyFrom: i to: j  
a.slice(i...j); a[i...j] "N/A" "N/A" c copyFrom: i to: j-1 "a でも可"
a.sort a asSortedArray c sortBy: [:a :b|a<=b]  
      c asSortedArray
      c asSortedCollection "a でも可"
a.sort! a sort "N/A"  
a.reverse a reverse c reverse  
a.reverse! "N/A" "N/A"  
a.uniq! "N/A" "N/A" c asSet. a asSet "重複不可コレクションへ"
a.join(d) "N/A" "N/A" "#do:separatedBy: を使用"
a.each {|x|...} a do: [:x|"..."] c do: [:x|"..."]  

破壊的操作の代替

いくつかの破壊的操作に相当するものが欠けていますが、同等の非破壊操作と #become: を組み合わせて使用することで、相応の振る舞い(ただしコスト削減目的の場合を除く)をさせることは可能です。
| arrayA arrayB |
arrayA := #(1 2 3).
arrayB := arrayA.

arrayA := arrayA copyEmpty.

^ {arrayA. arrayB}   " => #(#() #(1 2 3)) "
| arrayA arrayB |
arrayA := #(1 2 3).
arrayB := arrayA.

arrayA become: arrayA copyEmpty.

^ {arrayA. arrayB}   " => #(#() #()) "

区切り文字で区切った文字列の生成

カンマなどで区切った文字列の生成について、#do:separatedBy: を使ったイディオムはこんなかんじでしょうか。
| a d |
a := #(1 2 3). d := $, .

^ String streamContents: [:s | a do: [:e | s nextPutAll: e printString] separatedBy: [s nextPut: d]]
=> '1,2,3'
ここで String が起動する SequenceableCollection class >> #streamContents: の定義はこんなの。ストリームの、これまたイディオム的な用い方をラップしただけなんですが、ワンライナーできて使いでがあり重宝します。
SequenceableCollection class >> streamContents: blockWithArg
   | stream |
   stream := WriteStream on: (self new: 100).
   blockWithArg value: stream.
   ^ stream contents
ここにある、ストリームを使ったイディオム自体も、配列や文字列のように固定長のコレクションを、その長さをあらかじめ決めずに(あるいは、決められない状態で)作らなくてはならないときによく使うので覚えておきたいですね。
| stream |
stream := String new writeStream.
stream nextPutAll: 'なんたら'.   "実際にはもっと込み入った文字列生成処理"
^ stream contents

重複排除

Array#uniq、Array#uniq! のようなメソッドはないので、要素の重複を許さないコレクション(a Set)に変換してもとのコレクションに戻してやることをよくします。もちろん、それなりにコストもかかります。
#(1 2 2 3 3 3) asSet asArray   " => #(1 2 3) "
さらに、a Set に変換した時点で順序情報は失われてしまいます。
#(3 3 3 2 2 1) asSet asArray   " => #(1 2 3) "
重複は排除しつつも、残った要素の位置関係を変えたくなければ、何かしらの手続きを記述する必要があるでしょう。たとえば先の #streamContents: を使って、こんなかんじに。
| a |
a := #(3 3 3 2 2 1).

^ Array streamContents: [:s | a do: [:e | (s contents includes: e) ifFalse: [s nextPut: e]]]
=> #(3 2 1)

ソート

#asSortedArray は、レシーバを(必要なら)配列(an Array)に変換し、それをソートした状態で返してきます。#asSortedCollection は、ソートされた状態を常に保っているコレクション(a SortedCollection)に変換して返します。
| collection |
collection := OrderedCollection with: 3 with: 2 with: 1.
^ collection asSortedArray   " => #(1 2 3) "
| collection |
collection := OrderedCollection with: 3 with: 2 with: 1.
^ collection asSortedCollection   " => a SortedCollection(1 2 3) "

#with:with:with:

何度も書いているので少々しつこいようですが、Smalltalk において、with: 3 with: 2 with: 1 はこれでひとつのメッセージで、このメッセージを Array や OrderedCollection(クラス)に送信することで、#with:with:with: という名前のメソッドを起動します(より正確には、そうした名前のメソッドを起動してくれることを“期待”します。実際にメッセージを受け取ったあとどう振る舞うか、は、レシーバしだいですw)。 したがって上の表で“要素が6つまで”とあるのは、Squeak システムの Smalltalk では、#with:、#with:with:、#with:with:with:、... 、#with:with:with:with:with:with: の6つのメソッドが定義されていることを意味します。もちろん、7つ以上の要素向けに、新たなメソッドを追加することも可能ですが、その前に、別の書き方(an Array なら #at:put: を使うとか、an OrderedCollection なら #add: を使うとか)を検討したほうがよいでしょう。 とにかく、たんに必要な数だけ with: を書き連ねればよいというわけではない…ということに注意してください。これは、たとえば「copyFrom: i to: j」というメッセージが「copyFrom: i」と「to: j」には分解できない(「copyFrom: i to: j」でひとつのメッセージで、#copyFrom:to: という名前のメソッドを起動する)のとも一緒です。

#at:put:

オリジナルの対応表にはありませんが、Ruby 同様 Smalltalk も、配列要素の置き換え(指定したインデックス位置への代入)はメソッドを介して行なわれます。
a at: 1 put: 10.
^ a   " => #(10 2 3) "
Ruby 同様?」と思われた方もおられるかもしれませんね。たしかに Ruby 、あたかも配列への代入の構文があるかのように見せています。が、じつはこれ、Smalltalk が at: 1 put: 10 というメッセージを送って #at:put: というメソッドを起動しているのとそっくりな方法で、#[]= というちょっと変わった名前のメソッドを起動しているのです。
a[0] = 10;
p a   #=> [10, 2, 3]
Smalltalk ふうに解釈すると、a に「[0] = 10」という2つ( 0 と 10 )の引数を添えたメッセージを送信し、#[]= というメソッドを起動…ということになりましょうか。Smalltalk 好きにはたまらない変態さですね。w