How do I merge two dictionaries in Smalltalk with a oneliner?
Asked Answered
H

6

9

There might exist dialect specific ways, or maybe a general one. I have two dictionaries, let's say:

a := {'a' -> 1} asDictionary.
b := {'b' -> 2} asDictionary.

Now I want to get c as the union of a and b.

Hydrangea answered 6/1, 2021 at 15:0 Comment(1)
Apart from the answers already given: "with a oneliner" - Just create a method for it if it doesn't statisfy the criteria. Readability comes first, way before cramming everything into one line.Lilian
B
5

It depends on whether you want a third object or you prefer to include, say b, into a.

For the first case

c := Dictionary new.
a keysAndValuesDo: [:k :v | c at: k put: v].
b keysAndValuesDo: [:k :v | c at: k put: v].

For the second

b keysAndValuesDo: [:k :v | a at: k put: v].

Note also that the operation is not commutative, meaning that if the same key occurs in both dictionaries, the one that will survive in the result is the last added.

Balkin answered 6/1, 2021 at 15:32 Comment(0)
T
3

Another one:

Dictionary new addAll: a; addAll: b; yourself

Also works when a and b are other collections of Associations, as in your question without asDictionary. Works at least in Squeak.

As pointed out by @aka.nice in the comments, depending on your Smalltalk implementation it may have the side effect of sharing the Association objects with the input dictionaries. It certainly is that way in Squeak. If a and b have common keys, it may even modify one of the input dictionaries (in Squeak a) because first an existing Association is added to the new Dictionary's hashtable, and then this Association gets the value from the other input Dictionary assigned.

a := Dictionary newFrom: {#a -> 1}.
b := Dictionary newFrom: {#a -> 2}.
c := Dictionary new addAll: a; addAll: b; yourself.
{a at: #a. b at: #a. c at: #a}  "==> #(2 2 2) in Squeak"
Tenebrific answered 6/1, 2021 at 20:45 Comment(5)
if a and b are already Dictionary, you can then addAll: a associations etc...Samhita
... with the nasty side effect however that associations will then be shared. Modifying a at: 'a' put: 4 will also modify c...Samhita
@Samhita The code also works without sending #associations if a and b are dictionaries.Tenebrific
oh great, I see how it works now. The funny thing is that associations are still shared. Moreover, in case of overlapping keys, the first dictionary (a) will share the association, and thus will be modified. Example: a := {'c'->0} as:Dictionary. b := {'c'->7} as:Dictionary. Dictionary new addAll: a; addAll: b; yourself. ^a at: 'c'. will answer 7Samhita
@Samhita Indeed, I edited that into the answer yesterday after replying to your comment :-)Tenebrific
S
3

In Squeak/Pharo you can simply use union:

a := {'a' -> 1} as: Dictionary.
b := {'b' -> 2} as: Dictionary.
c := a union: b.

-> a Dictionary('a'->1 'b'->2 )

Note that the elements of b will be chosen in case of overlapping keys

a := {'a' -> 1. 'c'->0} as:Dictionary.
b := {'b' -> 2. 'c'->7} as:Dictionary.
c := a union: b.

-> a Dictionary('a'->1 'b'->2 'c'->7 )
Samhita answered 6/1, 2021 at 23:26 Comment(0)
H
2

c := a, b works in Pharo (but not in Dolphin)

Hydrangea answered 6/1, 2021 at 15:7 Comment(2)
I don't like it... the binary message , so far has only ever been used for sequences (SequenceableCollection) for which it is very natural. Dictionary are unordered and overlapping keys will be fused. Considering that Pharo has union: too, that's a poor choice IMO.Samhita
Note that this poor choice was made in Squeak originally, not in Pharo.Samhita
B
2

I think Leandro's and aka.nice's answer provide you with nice solutions. Both also mention that you are going to lose information when the key is same for both of the dictionaries.

I'm writing this in order to complement these answers. If you should need to keep the duplicate key -> value I would do it the following way:

a := {'a' -> 1. 'c'->3} as: Bag.
b := {'b' -> 2. 'c'->20} as: Bag.
c := a union: b.

Which will give you a Bag with Dictionary as contents:

Dictionary('b'->2->1 'a'->1->1 'c'->20->1 'c'->3->1)

(I'm using Smalltalk/X-jv)

Bruns answered 7/1, 2021 at 10:24 Comment(0)
S
0

Here's a one-liner that should work in any Smalltalk, but might share associations:

 c := (a associations, b associations) asDictionary

Next is a version that should work in any Smalltalk without risk of sharing associations,
and is what both (c := a, b) and (c := a union: b) should be doing in all of them:


  c := (a associations, b associations)
            inject: Dictionary new
              into: [:all :each|
                     all at: each key put: each value
                         ; yourself
                    ]

Finally, this last one should also work in any version of Smalltalk,
without risk of either sharing associations or losing any information:


  c := (a associations, b associations)
            inject: Dictionary new
              into: [:all :each|
                     (all at: each key ifAbsentPut: List new)
                          add: each value
                          ; yourself
                    ]
Swaine answered 26/6, 2023 at 1:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.