LampSort

As an example, we’ll be using LampSort, a non-recursive QuickSort implementation. Please follow the link to read up about this cool variant.

In what follows, we are interested in visualising a sorting algorithm and simplify code to that end. Try to image how you can go from here to a more complex, real world application.

Object Logging

Unlike traditional logging where you generate strings, object logging is a technique where we reify what we want to log into a real object. Think of this object as representing the event that happened.

It is up to you to decide what to log and which information to include. There are no rules. Don’t be cheap, it is hard to predict what will be needed later on. What is not included is lost forever.

In real applications you will probably already have most of the data that you want to include at hand, so the cost is acceptable. Just be careful with structure sharing and destructive modifications.

Once the log object is created, you should send it off to whoever is interested, if anyone. It turns out we already have a pretty good mechanism for doing that in Pharo, namely Announcements. We might as well use that to emit log event objects.

LampSortLogEvent

We begin by setting up a class hierarchy of log objects. Here is our root class, LampSortLogEvent.

Announcement subclass: #LampSortLogEvent

instanceVariableNames: ‘data’

classVariableNames: ‘LampSortLogEventAnnouncer’

category: ‘LampSortVisualized-LogEvents’

Each log event will hold a copy of the data array that we are sorting, at the moment the log event was emitted.

data: sequenceableCollection

data := sequenceableCollection copy

On our class side we’ll maintain a unique instance of an Announcer, the one we’ll be using to notify interested consumers of log events that are emitted.

announcer

^ self class announcer emit

self announcer announce: self

And on the class side we have a lazy initialisation for the Announcer.

announcer

^ LampSortLogEventAnnouncer

ifNil: [

LampSortLogEventAnnouncer := Announcer new ]

Here is one of the concrete log event subclasses, LampSortPivotSelected. It feels natural to name these using past tense. This particular event is emitted when the algorithm selects a pivot from a sub interval of the data, which is always the first element of the interval is this particular version of the algorithm.

LampSortLogEvent subclass: #LampSortPivotSelected

instanceVariableNames: ‘interval’

classVariableNames: ‘’

category: ‘LampSortVisualized-LogEvents’

It is important to give each log event a good printOn: implementation. This main printed representation will be used in several context later on. Make it clear, to the point and not too long.

pivotIndex

^ interval first pivotValue

^ data at: self pivotIndex printOn: stream

stream

<< ‘Selected pivot ‘; print: self pivotValue;

<< ‘ @ ‘; print: self pivotIndex;

<< ‘ from ‘; print: interval

Along the same lines we define a couple of others, one for each of the important steps in the LampSort algorithm:

LampSortPartitioned (left, right)

(left, right) LampSortSwapped (oneIndex, anotherIndex)

(oneIndex, anotherIndex) LampSortIntervalSelected (interval, intervals)

(interval, intervals) LampSortIntervalRemoved (interval, intervals)

(interval, intervals) LampSortIntervalsAdded (newIntervals, intervals)

The last 3 will inherit from LampSortIntervalsEvent because they all hold the intevals set. It is a bit of work, but with the code generation tools you can automate most of the coding.

Here is how the log event objects are used.

LampSortPivotSelected new

data: data;

interval: interval;

emit

Just make a new instance, set the necessary properties and send it emit.

LampSortInstrumented

We are now ready to use the log event objects that we defined. The original LampSort implementation consists of just 2 elegant methods, sort and partition: — read the original article explaining LampSort for an detailed explanation.

To maintain that easy to read version, we are going to create a new one, LampSortInstrumented, where we introduce helper methods for each important step in the algorithm.

sort

| intervals one |

intervals := Set with: (1 to: data size).

[ intervals isEmpty ]

whileFalse: [

one := self selectIntervalFrom: intervals.

one size > 1

ifTrue: [

self

joinIntervals: intervals

with: (self partition: one) ].

self removeInterval: one from: intervals ] partition: interval

| pivot index |

pivot := self selectFirstElementAsPivot: interval.

self swap: interval first with: interval last.

index := interval first.

interval first to: interval last — 1 do: [ :each |

(data at: each) < pivot

ifTrue: [

self swap: each with: index.

index := index + 1 ] ].

self swap: interval last with: index.

^ self splitInterval: interval around: index

In each helper methods, we’ll do 2 things:

create and emit a log event do whatever has to be done in that step

All in all, there are 6 helper methods.

selectIntervalFrom: intervals

| selection |

selection := intervals anyOne.

LampSortIntervalSelected new

data: data;

intervals: intervals;

interval: selection;

emit.

^ selection removeInterval: interval from: intervals

LampSortIntervalRemoved new

data: data;

intervals: intervals;

interval: interval;

emit.

intervals remove: interval selectFirstElementAsPivot: interval

LampSortPivotSelected new

data: data;

interval: interval;

emit.

^ data at: interval first swap: oneIndex with: anotherIndex

LampSortSwapped new

data: data;

oneIndex: oneIndex;

anotherIndex: anotherIndex;

emit.

data swap: oneIndex with: anotherIndex splitInterval: interval around: pivotIndex

| left right |

left := interval first to: pivotIndex — 1.

right := pivotIndex + 1 to: interval last.

LampSortPartitioned new

data: data;

left: left;

right: right;

emit.

^ Array with: left with: right

Note how in the last method, the left and right intervals were already at hand, created as part of the algorithm. We just use them in the log object at no cost.

This concludes the instrumentation of our algorithm. In your own applications, you can do what you want to make the generation of log events as convenient as possible.

Textual Logging

One aspect of object logging is that it is compatible with traditional, textual logging. How ? By using the print representations. Here is how to log to the classic Pharo Transcript.

LampSortLogEvent announcer

subscribe: LampSortLogEvent

do: [ :event | Transcript crShow: event ]. LampSortInstrumented new sort: #(7 12 3 20 5 8 2) copy.

We subscribe to all Announcements that are subclasses of LampSortLogEvent and show their printed representation on the Transcript. This is the result.

We can use to same technique to append to a log file or the system log. In this simplified demo, our log event objects do not contain their own timestamp — this and any other relevant information is easy to add.

Inspecting Log Events

The standard GT-Inspector tool (part of The Glamorous Toolkit) makes it easy to look at Announcements generated by any Announcer, thus we can use it to look at our log event objects. Here is how to get started.

LampSortLogEvent announcer inspect. LampSortInstrumented new sort: #(7 12 3 20 5 8 2) copy.

Inspect the LampSortLogEvent announcer and run the algorithm on an example array. The array gets copied because the algorithm sorts destructively in-place.