flatten

某スレでちょっと flatten がらみの話があったのをみて、そういえば、Squeak システムの Smalltalk には(と言うのは面倒なので、普通、単に Squeak には…と言います。でもこう言うと知らない人は Squeak という言語があるように読んでしまっていけません…)Ruby の flatten みたいのがないなぁ…と常々思っていたのを思い出したので、ちょっとまじめに調べてみました。

で、ググりつつ、行き着いたのが Konz さんのこの投稿。

http://lists.squeakfoundation.org/pipermail/squeak-dev/2003-June/060772.html

まあ、ちょっと運用が面倒ですが、似たような機能はあるみたいですね。

FlattenEncoder stream process: #(1 2 3 (1 2 3 (1 2 3))) 

でいけます。ただ、Ruby と違って Squeak システムの Smalltalk では(くどいっ)文字列もコレクションなので、

FlattenEncoder stream process: #(this (is a (pen))) 
  =>  an OrderedCollection($t $h $i $s $i $s $a $p $e $n) 

こんなふうに文字列やシンボルまで flatten されてしまいます。しかたがないので要素が、外枠と同じ species の場合だけ flatten する SpeciesSpecificFlattenEncoder(ながっ)を作りましょう。

FlattenEncoder subclass: #SpeciesSpecificFlattenEncoder
  instanceVariableNames: 'species '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Morphic-Postscript Filters'

SpeciesSpecificFlattenEncoder >> write: anObject
  anObject species == (species ifNil: [species _ anObject species])
    ifTrue: [super write: anObject] ifFalse: [self writeObject: anObject] 

これでめでたく、期待通りの flatten ができます。

SpeciesSpecificFlattenEncoder stream process: #(this (is a (pen)))
  => an OrderedCollection(#this #is #a #pen) 

まああと、SequenceableCollection >> #flatten を定義しておくほうが親切ですね。

SequenceableCollection >> flatten
  | flatten |
  flatten _ SpeciesSpecificFlattenEncoder stream process: self.
  ^ flatten as: self species 

こんなんで Ruby に追いつけましたでしょうか。

#(this (is a (pen))) flatten
  => #(this is a pen) 

これで話題にのぼったスクリプトも(効率を無視すれば)比較的素直に記述できます。

array flatten
array _ #( ('apple' 'orange' 'mango') ('kiwi' 'banana' 'apple') ('kiwi' 'melon')). flatten _ array flatten asOrderedCollection. ^ (flatten removeAll: flatten asSet; yourself) asSet => a Set('kiwi' 'apple')

もちろん、一層の flatten なら #inject:into: でその場で書けるので別に flatten をわざわざこしらえるまでもないような気もしますが…。

array flatten
array _ #( ('apple' 'orange' 'mango') ('kiwi' 'banana' 'apple') ('kiwi' 'melon')). flatten _ array inject: OrderedCollection new into: [: sum : each | sum addAll: each; yourself]. ^ (flatten removeAll: flatten asSet; yourself) asSet => a Set('kiwi' 'apple')