Skip to main content
Engineering LibreTexts

9.4: Examples of Key Classes

  • Page ID
    36381
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)

    We present now the most common or important collection classes using simple code examples. The main protocols of collections are: at:, at:put: — to access an element, add:, remove: — to add or remove an element, size, isEmpty, include: — to get some information about the collection, do:, collect:, select: — to iterate over the collection. Each collection may implement or not such protocols, and when they do, they interpret them to fit with their semantics. We suggest you browse the classes themselves to identify specific and more advanced protocols.

    We will focus on the most common collection classes: OrderedCollection, Set, SortedCollection, Dictionary, Interval, and Array.

    Common creation protocol. There are several ways to create instances of collections. The most generic ones use the methods new: and with:. new: anInteger creates a collection of size anInteger whose elements will all be nil. with: anObject creates a collection and adds anObject to the created collection. Different collections will realize this behaviour differently.

    You can create collections with initial elements using the methods with:, with:with: etc. for up to six elements.

    Array with: 1    → #(1)
    Array with: 1 with: 2    → #(1 2)
    Array with: 1 with: 2 with: 3    → #(1 2 3)
    Array with: 1 with: 2 with: 3 with: 4    → #(1 2 3 4)
    Array with: 1 with: 2 with: 3 with: 4 with: 5    → #(1 2 3 4 5)
    Array with: 1 with: 2 with: 3 with: 4 with: 5 with: 6    → #(1 2 3 4 5 6)
    

    You can also use addAll: to add all elements of one kind of collection to another kind:

    (1 to: 5) asOrderedCollection addAll: '678'; yourself
        →    an OrderedCollection(1 2 3 4 5 $6 $7 $8)
    

    Take care that addAll: also returns its argument, and not the receiver! You can also create many collections with withAll: or newFrom:

    ArraywithAll:#(7 3 1 3)                    →    #(7 3 1 3)
    OrderedCollection withAll: #(7 3 1 3)      →    an OrderedCollection(7 3 1 3)
    SortedCollection withAll: #(7 3 1 3)       →    a SortedCollection(1 3 3 7)
    SetwithAll:#(7 3 1 3)                      →    a Set(7 1 3)
    BagwithAll:#(7 3 1 3)                      →    a Bag(7 1 3 3)
    Dictionary withAll: #(7 3 1 3)             →    a Dictionary(1->7 2->3 3->1 4->3)
    
    ArraynewFrom:#(7 3 1 3)                                    → #(7 3 1 3)
    OrderedCollection newFrom: #(7 3 1 3)                      → an OrderedCollection(7 3 1 3)
    SortedCollection newFrom: #(7 3 1 3)                       → a SortedCollection(1 3 3 7)
    Set newFrom: #(7 3 1 3)                                    → a Set(7 1 3)
    Bag newFrom: #(7 3 1 3)                                    → a Bag(7 1 3 3)
    Dictionary newFrom: {1 -> 7. 2 -> 3. 3 -> 1. 4 -> 3}       → a Dictionary(1->7 2->3 3->1 4->3)
    

    Note that these two methods are not identical. In particular, Dictionary class»withAll: interprets its argument as a collection of values, whereas Dictionary class»newFrom: expects a collection of associations.

    Array

    An Array is a fixed-sized collection of elements accessed by integer indices. Contrary to the C convention, the first element of a Smalltalk array is at position 1 and not 0. The main protocol to access array elements is the method at: and at:put:. at: anInteger returns the element at index anInteger. at: anInteger put: anObject puts anObject at index anInteger. Arrays are fixed-size collections therefore we cannot add or remove elements at the end of an array. The following code creates an array of size 5, puts values in the first 3 locations and returns the first element.

    anArray := Array new: 5.
    anArray at: 1 put: 4.
    anArray at: 2 put: 3/2.
    anArray at: 3 put: 'ssss'.
    anArray at: 1    → 4
    

    There are several ways to create instances of the class Array. We can use new:, with:, and the constructs #( ) and { }.

    Creation with new: new: anInteger creates an array of size anInteger. Array new: 5 creates an array of size 5.

    Creation with with: with: methods allows one to specify the value of the elements. The following code creates an array of three elements consisting of the number 4, the fraction 3/2 and the string 'lulu'.

    Array with: 4 with: 3/2 with: 'lulu'    →    {4 . (3/2) . 'lulu'}
    

    Literal creation with #(). #() creates literal arrays with static (or “literal”) elements that have to be known when the expression is compiled, and not when it is executed. The following code creates an array of size 2 where the first element is the (literal) number 1 and the second the (literal) string 'here'.

    #(1 'here') size    →    2
    

    Now, if you evaluate #(1+2), you do not get an array with a single element 3 but instead you get the array #(1 #+ 2) i.e., with three elements: 1, the symbol #+ and the number 2.

    #(1+2)    →    #(1 #+ 2)
    

    This occurs because the construct #() causes the compiler to interpret literally the expressions contained in the array. The expression is scanned and the resulting elements are fed to a new array. Literal arrays contain numbers, nil, true, false, symbols and strings.

    Dynamic creation with { }. Finally, you can create a dynamic array using the construct {}. { a . b } is equivalent to Array with: a with: b. This means in particular that the expressions enclosed by { and } are executed.

    {1+2}    →    #(3)
    {(1/2) asFloat} at: 1    →    0.5
    {10 atRandom . 1/3} at: 2    →    (1/3)
    

    Element Access. Elements of all sequenceable collections can be accessed with at: and at:put:.

    anArray := #(1 2 3 4 5 6) copy.
    anArray at: 3    →    3
    anArray at: 3 put: 33.
    anArray at: 3    →    33
    

    Be careful with code that modifies literal arrays! The compiler tries to allocate space just once for literal arrays. Unless you copy the array, the second time you evaluate the code your “literal” array may not have the value you expect. (Without cloning, the second time around, the literal #(1 2 3 4 5 6) will actually be #(1 2 33 4 5 6)!) Dynamic arrays do not have this problem.

    OrderedCollection

    OrderedCollection is one of the collections that can grow, and to which elements can be added sequentially. It offers a variety of methods such as add:, addFirst:, addLast:, and addAll:.

    ordCol := OrderedCollection new.
    ordCol add: 'Seaside'; add: 'SqueakSource'; addFirst: 'Monticello'.
    ordCol    →    an OrderedCollection('Monticello' 'Seaside' 'SqueakSource')
    

    Removing Elements. The method remove: anObject removes the first occurrence of an object from the collection. If the collection does not include such an object, it raises an error.

    ordCol add: 'Monticello'.
    ordCol remove: 'Monticello'.
    ordCol    →    an OrderedCollection('Seaside' 'SqueakSource' 'Monticello') 
    

    There is a variant of remove: named remove:ifAbsent: that allows one to specify as second argument a block that is executed in case the element to be removed is not in the collection.

    res := ordCol remove: 'zork' ifAbsent: [33].
    res    →    33
    

    Conversion. It is possible to get an OrderedCollection from an Array (or any other collection) by sending the message asOrderedCollection:

    #(1 2 3) asOrderedCollection    →    an OrderedCollection(1 2 3)
    'hello' asOrderedCollection    →    an OrderedCollection($h $e $l $l $o) 
    

    Interval

    The class Interval represents ranges of numbers. For example, the interval of numbers from 1 to 100 is defined as follows:

    Interval from: 1 to: 100    →    (1 to: 100) 
    

    The printString of this interval reveals that the class Number provides us with a convenience method called to: to generate intervals:

    (Interval from: 1 to: 100) = (1 to: 100)    →    true
    

    We can use Interval class»from:to:by: or Number»to:by: to specify the step between two numbers as follow:

    (Interval from: 1 to: 100 by: 0.5) size    →    199
    (1 to: 100 by: 0.5) at: 198    →    99.5
    (1/2 to: 54/7 by: 1/3) last    →    (15/2)
    

    Dictionary

    Dictionaries are important collections whose elements are accessed using keys. Among the most commonly used messages of dictionary you will find at:, at:put:, at:ifAbsent:, keys and values.

    colors := Dictionary new.
    colors at: #yellow put: Color yellow.
    colors at: #blue put: Color blue.
    colors at: #red put: Color red.
    colors at: #yellow    →    Color yellow
    colors keys           →    a Set(#blue #yellow #red)
    colors values         →    {Color blue . Color yellow . Color red}
    

    Dictionaries compare keys by equality. Two keys are considered to be the same if they return true when compared using =. A common and difficult to spot bug is to use as key an object whose = method has been redefined but not its hash method. Both methods are used in the implementation of dictionary and when comparing objects.

    The class Dictionary clearly illustrates that the collection hierarchy is based on subclassing and not subtyping. Even though Dictionary is a subclass of Set, we would normally not want to use a Dictionary where a Set is expected. In its implementation, however, a Dictionary can clearly be seen as consisting of a set of associations (key value) created using the message ->. We can create a Dictionary from a collection of associations, or we may convert a dictionary to an array of associations.

    colors := Dictionary newFrom: { #blue->Color blue. #red->Color red.
        #yellow->Color yellow }.
    colors removeKey: #blue.
    colors associations    →    {#yellow->Color yellow . #red->Color red}
    

    IdentityDictionary. While a dictionary uses the result of the messages = and hash to determine if two keys are the same, the class IdentityDictionary uses the identity (message ==) of the key instead of its values, i.e., it considers two keys to be equal only if they are the same object.

    Often Symbols are used as keys, in which case it is natural to use an IdentityDictionary, since a Symbol is guaranteed to be globally unique. If, on the other hand, your keys are Strings, it is better to use a plain Dictionary, or you may get into trouble:

    a := 'foobar'.
    b := a copy.
    trouble := IdentityDictionary new.
    trouble at: a put: 'a'; at: b put: 'b'.
    trouble at: a            →    'a'
    trouble at: b            →    'b'
    trouble at: 'foobar'     →    'a'
    

    Since a and b are different objects, they are treated as different objects. Interestingly, the literal 'foobar' is allocated just once, so is really the same object as a. You don’t want your code to depend on behaviour like this! A plain Dictionary would give the same value for any key equal to 'foobar'.

    Use only globally unique objects (like Symbols or SmallIntegers) as keys for a IdentityDictionary, and Strings (or other objects) as keys for a plain Dictionary.

    Note that the global Smalltalk is an instance of SystemDictionary, a subclass of IdentityDictionary, hence all its keys are Symbols (actually, ByteSymbols, which contain only 8-bit characters).

    Smalltalk keys collect: [ :each | each class ]    →    a Set(ByteSymbol)
    

    Sending keys or values to a Dictionary results in a Set, which we look at next.

    Set

    The class Set is a collection which behaves as a mathematical set, i.e., as a collection with no duplicate elements and without any order. In a Set elements are added using the message add: and they cannot be accessed using the message at:. Objects put in a set should implement the methods hash and =.

    s := Set new.
    s add: 4/2; add: 4; add:2.
    s size    →    2
    

    You can also create sets using Set class»newFrom: or the conversion message Collection»asSet:

    (Set newFrom: #(1 2 3 1 4)) = #(1 2 3 4 3 2 1) asSet    →    true
    

    asSet offers us a convenient way to eliminate duplicates from a collection:

    { Color black. Color white. (Color red + Color blue + Color green) } asSet size    →    2 
    

    Note that red + blue + green = white.

    A Bag is much like a Set except that it does allow duplicates:

    { Color black. Color white. (Color red + Color blue + Color green) } asBag size    →    3 
    

    The set operations union, intersection and membership test are implemented by the Collection messages union:, intersection: and includes:. The receiver is first converted to a Set, so these operations work for all kinds of collections!

    (1 to: 6) union: (4 to: 10)        →    a Set (1 2 3 4 5 6 7 8 9 10)
    'hello' intersection: 'there'      →    'he'
    #Smalltalk includes: $k            →    true
    

    As we explain below, elements of a set are accessed using iterators (see Section 9.5).

    SortedCollection

    In contrast to an OrderedCollection, a SortedCollection maintains its elements in sort order. By default, a sorted collection uses the message <= to establish sort order, so it can sort instances of subclasses of the abstract class Magnitude, which defines the protocol of comparable objects (<, =, >, >=, between:and:...). (See Chapter 8.)

    You can create a SortedCollection by creating a new instance and adding elements to it:

    SortedCollection new add: 5; add: 2; add: 50; add: -10; yourself.
        →    a SortedCollection(-10 2 5 50) 
    

    More usually, though, one will send the conversion message asSortedCollection to an existing collection:

    #(5 2 50 -10) asSortedCollection    →    a SortedCollection(-10 2 5 50) 
    

    This example answers the following FAQ:

    FAQ: How do you sort a collection?

    Answer: Send the message asSortedCollection to it.

    'hello' asSortedCollection    →    a SortedCollection($e $h $l $l $o)
    

    How do you get a String back from this result? asString unfortunately returns the printString representation, which is not what we want:

    'hello' asSortedCollection asString    →    'a SortedCollection($e $h $l $l $o)' 
    

    The correct answer is to either use String class»newFrom:, String class»withAll: or Object»as::

    'hello' asSortedCollection as: String            →    'ehllo'
    String newFrom: ('hello' asSortedCollection)     →    'ehllo'
    String withAll: ('hello' asSortedCollection)     →    'ehllo'
    

    It is possible to have different kinds of elements in a SortedCollection as long as they are all comparable. For example we can mix different kinds of numbers such as integers, floats and fractions:

    { 5. 2/--3. 5.21 } asSortedCollection    →    a SortedCollection((--2/3) 5 5.21) 
    

    Imagine that you want to sort objects that do not define the method <= or that you would like to have a different sorting criterion. You can do this by supplying a two argument block, called a sortblock, to the sorted collection. For example, the class Color is not a Magnitude and it does not implement the method <=, but we can specify a block stating that the colors should be sorted according to their luminance (a measure of brightness).

    col := SortedCollection sortBlock: [:c1 :c2 | c1 luminance <= c2 luminance].
    col addAll: { Color red. Color yellow. Color white. Color black }.
    col    →    a SortedCollection(Color black Color red Color yellow Color white)
    

    String

    A Smalltalk String represents a collection of Characters. It is sequenceable, indexable, mutable and homogeneous, containing only Character instances. Like Arrays, Strings have a dedicated syntax, and are normally created by directly specifying a String literal within single quotes, but the usual collection creation methods will work as well.

    'Hello'                                 →    'Hello'
    String with: $A                         →    'A'
    String with: $h with: $i with: $!       →    'hi!'
    String newFrom: #($h $e $l $l $o)       →    'hello' 
    

    In actual fact, String is abstract. When we instantiate a String we actually get either an 8-bit ByteString or a 32-bit WideString. To keep things simple, we usually ignore the difference and just talk about instances of String.

    Two instances of String can be concatenated with a comma.

    s := 'no', ' ', 'worries'.
    s    →    'no worries'
    

    Since a string is a mutable collection we can also change it using the method at:put:.

    s at: 4 put: $h; at: 5 put: $u.
    s    →    'nohurries' 
    

    Note that the comma method is defined by Collection, so it will work for any kind of collection!

    (1 to: 3),'45'    →    #(1 2 3 $4 $5)
    

    We can also modify an existing string using replaceAll:with: or replaceFrom:to:with: as shown below. Note that the number of characters and the interval should have the same size.

    s replaceAll: $n with: $N.
    s    →    'Nohurries'
    s replaceFrom: 4 to: 5 with: 'wo'.
    s    →    'No worries'
    

    In contrast to the methods described above, the method copyReplaceAll: creates a new string. (Curiously, here the arguments are substrings rather than individual characters, and their sizes do not have to match.)

    s copyReplaceAll: 'rries' with: 'mbats'    →    'No wombats' 
    

    A quick look at the implementation of these methods reveals that they are defined not only for Strings, but for any kind of SequenceableCollection, so the following also works:

    (1 to: 6) copyReplaceAll: (3 to: 5) with: { 'three'. 'etc.' }    →    #(1 2 'three' 'etc.' 6) 
    

    String matching. It is possible to ask whether a pattern matches a string by sending the match: message. The pattern can specify * to match an arbitrary series of characters and # to match a single character. Note that match: is sent to the pattern and not the string to be matched.

    'Linux *' match: 'Linux mag'                →    true
    'GNU/Linux #ag' match: 'GNU/Linux tag'      →    true
    

    Another useful method is findString:.

    'GNU/Linux mag' findString: 'Linux'                                        →    5
    'GNU/Linux mag' findString: 'linux' startingAt: 1 caseSensitive: false     →    5 
    

    More advanced pattern matching facilities offering the capabilities of Perl are also available, but they are not included in the standard image.1

    Some tests on strings. The following examples illustrate the use of isEmpty, includes: and anySatisfy: which are further messages defined not only on Strings but more generally on collections.

    'Hello' isEmpty    →    false
    'Hello' includes: $a    →    false
    'JOE' anySatisfy: [:c | c isLowercase]    →    false
    'Joe' anySatisfy: [:c | c isLowercase]    →    true 
    

    String templating. There are three messages that are useful to manage string templating: format:, expandMacros and expandMacrosWith:.

    '{1} is {2}' format: {'Squeak' . 'cool'}    →    'Squeak is cool'
    

    The messages of the expandMacros family offer variable substitution, using <n> for carriage return, <t> for tabulation, <1s>, <2s>, <3s> for arguments (<1p>, <2p>, surrounds the string with single quotes), and <1?value1:value2> for conditional.

    'look-<t>-here' expandMacros                                →    'look- -here'
    '<1s> is <2s>' expandMacrosWith: 'Squeak' with: 'cool'      →    'Squeak is cool'
    '<2s> is <1s>' expandMacrosWith: 'Squeak' with: 'cool'      →    'cool is Squeak'
    '<1p> or <1s>' expandMacrosWith: 'Squeak' with: 'cool'      →    '"Squeak" or Squeak'
    '<1?Quentin:Thibaut> plays' expandMacrosWith: true          →    'Quentin plays'
    '<1?Quentin:Thibaut> plays' expandMacrosWith: false         →    'Thibaut plays'
    

    Some other utility methods. The class String offers numerous other utilities including the messages asLowercase, asUppercase and capitalized.

    'XYZ' asLowercase        →    'xyz'
    'xyz' asUppercase        →    'XYZ'
    'hilaire' capitalized    →    'Hilaire'
    '1.54' asNumber          →    1.54
    'this sentence is without a doubt far too long' contractTo: 20    →    'this sent...too long'
    

    Note that there is generally a difference between asking an object its string representation by sending the message printString and converting it to a string by sending the message asString. Here is an example of the difference.

    #ASymbol printString    →    '#ASymbol'
    #ASymbol asString       →    'ASymbol'
    

    A symbol is similar to a string but is guaranteed to be globally unique. For this reason symbols are preferred to strings as keys for dictionaries, in particular for instances of IdentityDictionary. See also Chapter 8 for more about String and Symbol.


    1. We strongly recommend Vassili Bykov’s regular expression package, available at www.squeaksource.com/Regex.html.


    This page titled 9.4: Examples of Key Classes is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Andrew P. Black, Stéphane Ducasse, Oscar Nierstrasz, Damien Pollet via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.