Thursday's blog a little late here. Playing catchup.
One day, a while back, I had put together a "model" of some real world phenomena that I was pasting a UI on top of. Part of it was a pretty simple thing where an Array did the model well. There was a bit of code where the index of the elements was important too.
Then they pulled a fast one on me: "sometimes, it's possible for one of the intermediate slots to just not be there." I sighed. Noodled for a little bit. And then decided to just use a Dictionary with Integer lookup keys. Doing so was kind of enlightening; I suddenly thought about the problem with a higher level of APIs, things like keysAndValuesDo:, etc. At this point, it donned on me that SequenceableCollections (or Sequences for short) are just Dictionaries with specialized key domains. Adding a variety of the dictionary APIs to Sequence enables more mental tools to use on your model. And that's one of the things Smalltalk is all about: giving you the programmer better tools to bridge the way your brain wants to model problems with the way the computer does. The following methods (and tests) should be self explanatory, because they all come from Dictionary.
The Essential Methods
at: anIndex ifAbsent: aBlock
^[self at: anIndex] on: SubscriptOutOfBoundsError do: [:ex | aBlock value]
keys
^1 to: self size
Methods that are easy after that
associationsDo: aBlock
self keysAndValuesDo: [:key :val | aBlock value: key -> val]
includesKey: aNumber
^self keys includes: aNumber
keysDo: aBlock
self keys do: [:i | aBlock value: i]
at: aKey ifPresent: aBlock
| val |
val := self at: aKey ifAbsent: [^nil].
^aBlock value: val
Honorable Mention
keysAndValuesDo: aBlock
"Evaluate aBlock with each of the receiver's key/value pairs
(e.g. indexes and elements) as the arguments."
1 to: self size do: [:i | aBlock value: i value: (self at: i)]
Wait a minute, that's not an extension! That's in the base! Well it is since 7.0. I submitted it then; it was the original inspirator for the rest of these. I wish that it's siblings would make it into the base as well.
The Tests
testGenericAtIfAbsent
| a |
a := (11 to: 20) asArray reverse.
self
assert: (a at: 3 ifAbsent: [#hi]) = 18;
assert: (a at: -1 ifAbsent: [#hi]) = #hi;
assert: (a at: 20 ifAbsent: [#hi]) = #hi
testGenericAssociationsDo
| coll keys values |
coll := Array
with: 'abc'
with: 'xyz'
with: 'ijk'.
keys := OrderedCollection new.
values := OrderedCollection new.
coll associationsDo:
[:assoc |
keys addLast: assoc key.
values addLast: assoc value].
self
sequence: values sameAs: #('abc' 'xyz' 'ijk');
sequence: keys sameAs: (1 to: 3)
testGenericIncludesKey
self
assert: ((OrderedCollection with: 5 with: 3) includesKey: 2);
deny: ((Array new: 5) includesKey: 6);
deny: ((List new: 1) includesKey: 0)
testAtIfPresent
| val |
val := nil.
#(2) at: 1 ifPresent: [:each | val := 2].
self assert: val = 2.
#(2) at: 2 ifPresent: [:each | val := 0].
self deny: val = 0.
((Dictionary new)
at: 4 put: 5;
at: 6 put: 7;
yourself) at: 6 ifPresent: [:each | val := each].
self assert: val = 7.
((Dictionary new)
at: 4 put: 5;
at: 6 put: 7;
yourself) at: 8 ifPresent: [:each | val := nil].
self deny: val isNil
Read: Let's Lookup another Collection of Extensions