# Definition of a New Collection¶

In this chapter, we implement a data structure called a *sorted
sequence*. A sorted sequence is a sequence that automatically keeps the
elements of the sequence in a particular order, based on some value
computed from each element. Elements are added and removed from sorted
sequences; however, the sorted sequence determines the key associated
with the element. Thus, it does not make sense to store an element in a
sorted sequence at a specific key, because the sorted sequence will
determine the correct key to satisfy the automatic-ordering constraint.

We use Dylan’s `forward-iteration-protocol` to implement the connection
between our new collection class and Dylan’s standard collection generic
functions. Dylan’s forward-iteration protocol is a well-defined
interface that collection implementors and collection-iterator
implementors can use to enable iterators to operate over new
collections, and to enable collections to work with new iterators. Once
the forward iteration protocol is defined on `<sorted-sequence>`, many
of the standard Dylan collection generic functions that we covered in
*Collections and Control Flow*, will work with instances of the new sequence.

The airport application uses a sorted sequence to keep track of aircraft
transition in time order. See *The Airport Application*, for more details.

## The `sorted-sequence.dylan` file¶

The `sorted-sequence.dylan` file contains the module constants, classes,
and methods that build on Dylan’s collection framework to define the
structure and behavior of the new `<sorted-sequence>` collection.

### A new collection class¶

The `sorted-sequence.dylan` file.

```
module: sorted-sequence
define class <sorted-sequence> (<sequence>)
// The vector that stores the elements of the sorted sequence, in order
slot data :: <stretchy-vector> = make(<stretchy-vector>, size: 0);
// The function used to extract the comparison value from an element
constant slot value-function :: <function> = identity,
init-keyword: value-function:;
// The function used to determine whether one comparison value is
// smaller than another comparison value
constant slot comparison-function :: <function> = \<,
init-keyword: comparison-function:;
end class <sorted-sequence>;
```

Because is there is a well-defined ordering of the elements of sorted
sequences, we choose `<sequence>` to be the superclass of
`<sorted-sequence>`. We use the built-in collection class called
`<stretchy-vector>` to store the elements of our sorted sequence,
because we want to be able to have the sorted sequence grow to any size
in a convenient way.

The slots `comparison-function` and `value-function` are constant slots,
because we intend to have clients specify these functions only when they
create the sorted sequence. If we had decided to let clients change the
value of these slots, we would have made the slots virtual, so that we
could reorder the data vector after either function had changed.

Now that we have covered the structure and initialization of the sorted sequence data structure, we can define basic collection methods.

### Basic collection methods¶

The `sorted-sequence.dylan` file. *(continued)*

```
define method size (sorted-sequence :: <sorted-sequence>)
=> (sorted-sequence-size :: <integer>)
sorted-sequence.data.size;
end method size;
define method shallow-copy (sorted-sequence :: <sorted-sequence>)
=> (copy :: <sorted-sequence>)
let copy
= make(<sorted-sequence>,
value-function: sorted-sequence.value-function,
comparison-function: sorted-sequence.comparison-function);
// The map-into function replaces the elements of the copy’s data array
// to be the identical elements of the data array of sorted sequence
copy.data.size := sorted-sequence.data.size;
map-into(copy.data, identity, sorted-sequence.data);
copy;
end method shallow-copy;
define constant $unsupplied = list(#f);
define method element
(sorted-sequence :: <sorted-sequence>, key :: <integer>,
#key default = $unsupplied)
=> (element :: <object>);
if (key < sorted-sequence.data.size)
sorted-sequence.data[key];
elseif (default = $unsupplied)
error("Attempt to access key %= which is outside of %=.", key,
sorted-sequence);
else default;
end if;
end method element;
```

In the preceding code, we define methods for determining the number of
elements in the sorted sequence, for copying the sorted sequence (but
not the elements stored in the sorted sequence), and for accessing a
particular item in the sorted sequence. Once we have defined the
`element` method for sorted sequences, we can use the subscripting
syntax to access particular items in the sorted sequence. Our `element`
method implements the standard Dylan protocol, which allows the caller
to specify a default value if the key is not contained within the
collection. If the key is not part of the collection, and no default
value is specified, then an error is signaled. Since we do not export
`$unsupplied` from our library, we can be certain that no one can supply
that value as the `default` keyword parameter for our `element` method.

Note that the `element-setter` method is not defined, because it does
not make sense to store an element at a particular position within the
sorted sequence. The sorted sequence itself determines the correct key
for each item added to the sorted sequence, based on the item being
added and on the value and comparison functions.

Next, we show methods for adding and removing elements from sorted sequences.

### Adding and removing elements¶

The `sorted-sequence.dylan` file. *(continued)*

```
// Add an element to the sorted sequence
define method add!
(sorted-sequence :: <sorted-sequence>, new-element :: <object>)
=> (sorted-sequence :: <sorted-sequence>)
let element-value = sorted-sequence.value-function;
let compare = sorted-sequence.comparison-function;
add!(sorted-sequence.data, new-element);
sorted-sequence.data
:= sort!(sorted-sequence.data,
test: method (e1, e2)
compare(element-value(e1), element-value(e2))
end);
sorted-sequence;
end method add!;
// Remove the item at the top of the sorted sequence
define method pop (sorted-sequence :: <sorted-sequence>)
=> (top-of-sorted-sequence :: <object>)
let data-vector = sorted-sequence.data;
let top-of-sorted-sequence = data-vector[0];
let sorted-sequence-size = data-vector.size;
if (empty?(sorted-sequence))
error("Trying to pop empty sorted-sequence %=.", sorted-sequence);
else
// Shuffle up existing data, removing the top element from the
// sorted sequence
for (i from 0 below sorted-sequence-size - 1)
data-vector[i] := data-vector[i + 1];
end for;
// Decrease the size of the data vector, and return the top element
data-vector.size := sorted-sequence-size - 1;
top-of-sorted-sequence;
end if;
end method pop;
// Remove a particular element from the sorted sequence
define method remove!
(sorted-sequence :: <sorted-sequence>, value :: <object>,
#key test = \==, count = #f)
=> (sorted-sequence :: <sorted-sequence>)
let data-vector = sorted-sequence.data;
let sorted-sequence-size = data-vector.size;
for (deletion-point from 0,
// If we have reached the end of the sequence, or we have reached
// the user-specified limit, we are done
// Note that specifying a bound in the preceding clause for
// deletion-point does not work, because bounds are computed only
// once, and we change sorted-sequence-size in the body
until: (deletion-point >= sorted-sequence-size)
| (count & count = 0))
// Otherwise, if we found a matching element, remove it from the
// sorted sequence.
if (test(data-vector[deletion-point], value))
for (i from deletion-point below sorted-sequence-size - 1)
data-vector[i] := data-vector[i + 1]
end for;
sorted-sequence-size
:= (data-vector.size := sorted-sequence-size - 1);
if (count) count := count - 1 end;
end if;
end for;
sorted-sequence;
end method remove!;
```

The `remove!` method uses a form of the `for` loop that includes an
`until:` clause, much like the `my-copy-sequence` method defined in
*Lists and efficiency*. Note that all termination checks are tested
prior to the execution of the body.

Although the `pop` method is not used in the airport application, it is
included for completeness. We could make the `pop` method faster by
storing the data elements in reverse order; however, that would lead to
either odd behavior or odd implementation of the `element` function on
sorted sequences.

### The forward-iteration protocol¶

Dylan’s forward-iteration protocol allows us to connect the usual
collection iteration functions to our new collection class. Connecting
to the forward-iteration protocol is as simple as defining an
appropriate method for the `forward-iteration-protocol` generic
function. This method must return two objects and six functions.

The `sorted-sequence.dylan` file. *(continued)*

```
// This method enables many standard and user-defined collection operations
define method forward-iteration-protocol
(sorted-sequence :: <sorted-sequence>)
=> (initial-state :: <integer>, limit :: <integer>,
next-state :: <function>, finished-state? :: <function>,
current-key :: <function>, current-element :: <function>,
current-element-setter :: <function>, copy-state :: <function>)
values(
// Initial state
0,
// Limit
sorted-sequence.size,
// Next state
method (collection :: <sorted-sequence>, state :: <integer>)
state + 1
end,
// Finished state?
method (collection :: <sorted-sequence>, state :: <integer>,
limit :: <integer>)
state = limit;
end,
// Current key
method (collection :: <sorted-sequence>, state :: <integer>)
state
end,
// Current element
element,
// Current element setter
method (value :: <object>, collection :: <sorted-sequence>,
state :: <integer>)
error("Setting an element of a sorted sequence is not allowed.");
end,
// Copy state
identity);
end method forward-iteration-protocol;
```

If we are to iterate over any collection, we must maintain some state to
help the iterator remember the current point of iteration. For the
forward-iteration protocol, we maintain this state using any object
suitable for a given collection. In this case, an integer is sufficient
to maintain where we are in the iteration process. The first object
returned by `forward-iteration-protocol` is a state object that is
suitable for the start of an iteration. The second object returned is a
state object that represents the ending state of the iteration. Since,
in this case, the state object is just the current key of the sorted
sequence, the integer 0 is the correct initial state, and the integer
that represents the size of the collection is the correct ending state.

The third value returned is a function that takes the collection and the current iteration state, and returns a state that is the next step in the iteration. In this case, we can determine the next state simply by adding 1 to the current state.

The fourth value returned is a function that receives the collection, the current state, and the ending state, and that determines whether the iteration is complete. In this case, we need only to check whether the current state is equal to the ending state.

The fifth value returned is a function that generates the current key into the collection, given a collection and a state. In this case, the key is the state object.

The sixth value returned is a function that receives a collection and a
state, and returns the current element of the collection. In this case,
the `element` function is the obvious choice, since our state is just
the key.

The seventh value returned is a function that receives a new value, a
collection, and a state, and changes the current element to be the new
value. In this case, such an operation is illegal, since the only
rational way to add elements to sorted sequences is with `add!`.
Because this operation is illegal, an error is signaled.

The eighth and final value returned is a function that receives a collection and a state, and returns a copy of the state. In this case, we just return the state, because it is an integer and thus has no slots that are modified during the iteration process. If we represented the state with an object that had one or more slots that did change during iteration, we would have to make a new state instance and to copy the significant information from the old state instance to the new state instance.

Once we have defined a `forward-iteration-protocol` method for sorted
sequences, we can iterate over them using `for` loops, mapping
functions, and other collections iterators described in *Collections and Control Flow*.
Also, if someone defines a new iterator that uses the forward-iteration
protocol, then this new iterator will work with sorted sequences.

Dylan has several other related protocols for backward iteration and for
tables. See the *The Dylan Reference Manual* for details.

## The `sorted-sequence-library.dylan` file¶

The definitions for the sorted sequence library and module are simple.
The only module variable that we need to export is for the sorted
sequence class itself. All the generic functions that we want clients to
use on sorted sequences are exported by the `dylan` module.

The `sorted-sequence-library.dylan` file.

```
module: dylan-user
define library sorted-sequence
export sorted-sequence;
use dylan;
use definitions;
end library sorted-sequence;
define module sorted-sequence
export <sorted-sequence>;
use dylan;
use definitions;
end module sorted-sequence;
```

The `definitions` library and module are defined in *The Airport Application*.

## The `sorted-sequence.lid` file¶

The LID file for sorted sequences is also straightforward. The entire
library is contained within two files (in addition to the LID file
itself). The library and module definitions are in the file
`sorted-sequence-library.dylan`. The definitions of module constants,
classes, and methods are in the implementation file,
`sorted-sequence.dylan`.

The `sorted-sequence.lid` file.

```
library: sorted-sequence
files: sorted-sequence-library
sorted-sequence
```

## Summary¶

In this chapter, we covered the following:

- We explored how to define our own collection class.
- We showed how to integrate that class into Dylan’s collection framework.
- We used several variations of the control structures presented in
*Collections and Control Flow*.