die さんの 歪 経由で、高林さんの いやなブログ 発「配列操作の比較表: Ruby, Python, JavaScript, Perl, C++」をうけて。
似たような文法の言語間で混同しないように整理するのが、そもそもの目的のようなので、Smalltalk がごとき変態文法はお呼びでない…とも思いましたが、自らの Ruby 学習と、Squeak システムにおける Smalltalk を使った配列操作のご紹介を兼ねて項目を埋めてみました。
Ruby (Array) |
Squeak の Smalltalk (Array) |
Squeak の Smalltalk (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