Pythonクイズ(辞書の非破壊的操作) を Squeak Smalltalk の実装を参考に

以下の?????を埋めよ。但し文字数の指定はない。解答はいくつかあると思うが後日。

>>> d1 = {'a':1, 'b':2, 'c':3}
>>> d2 = {'c':4, 'd':5, 'e':6}
>>> print ?????
{'a': 1, 'c': 4, 'b': 2, 'e': 6, 'd': 5}
>>> d1
{'a': 1, 'c': 3, 'b': 2}
>>> d2
{'c': 4, 'e': 6, 'd': 5}
>>> d1.update(d2)
>>> d1
{'a': 1, 'c': 4, 'b': 2, 'e': 6, 'd': 5}
>>> d2
{'c': 4, 'e': 6, 'd': 5}

Pythonクイズ(辞書の非破壊的操作) - プログラミング日記


Squeak Smalltalk で同じことをするならこんな感じでしょうか。

| d1 d2 |
d1 := {#a->1. #b->2. #c->3} as: Dictionary.
d2 := {#c->4. #d->5. #e->6} as: Dictionary.

d1, d2.   "=> a Dictionary(#a->1 #b->2 #c->4 #d->5 #e->6 ) "
d1.       "=> a Dictionary(#a->1 #b->2 #c->3 ) "
d2.       "=> a Dictionary(#c->4 #d->5 #e->6 ) "

d1 addAll: d2.
d1.       "=> a Dictionary(#a->1 #b->2 #c->4 #d->5 #e->6 ) "
d2.       "=> a Dictionary(#c->4 #d->5 #e->6 ) "


Smalltalk では辞書(a Dictionary)はアソシエーション(an Association。key->value)を要素とするセット(a Set。順序なし、重複を許さないコレクション)として実現されているので、配列などの他のコレクションと同じように Collection>>#, をコールして非破壊的に結合できます。


ちなみに Collection>>#, の定義はこんな感じです。

Collection >> , aCollection
    ^self copy addAll: aCollection; yourself


なんてことはないですね。複製を作って #addAll:、つまり破壊的な結合(上の実行例でも使っています)をしているだけ。なお、Dictionary>>#addAll: はこんなふうな定義なので、

Dictionary >> addAll: aKeyedCollection
    aKeyedCollection == self ifFalse: [
        aKeyedCollection keysAndValuesDo: [:key :value |
            self at: key put: value]].
    ^aKeyedCollection

キーが同一のものがレシーバに存在する場合は上書きされます。



これを踏まえて Python で同様のことを試そうとすると…

>>> d1 = {'a':1, 'b':2, 'c':3}
>>> d2 = {'c':4, 'd':5, 'e':6}
>>> d1 + d2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
>>>


どうやら同じ「辞書」つながりと甘く見ていたら、少々勝手が違うみたいです(当たり前w)。ならば、複製して破壊的な結合をすればよいのでは…と、

>>> d1.copy().update(d2)
>>>

してみたのですが update() は返値を持たないので結果を出力してくれず、このお題には向きません。もっともこうした事態は Smalltalk でも #addAll: がレシーバを返さないことからもあらかじめ想定しておくべきでした。orz

Smalltalk でならここで ; yourself (「;」は直前のメッセージ式のレシーバにメッセージをたたみかけて送るための構文で、#yourself はレシーバを返すだけのメソッド。上の Collection>>#, の定義でも使われています)の出番なのですが、PythonOOP はメッセージングパラダイムではないので、こうした機能は無いはずです(じゃあメッセージングパラダイムRuby にはあるかっていうと無いんですけれども…^^;)。


しかたがないので、富豪的に。

>>> dict(d1.items() + d2.items())
{'a': 1, 'c': 4, 'b': 2, 'e': 6, 'd': 5}