Filters, Transformers, Custom Comparators and Custom Collections
Person Class
Imagine you have a class like so:
Loading Basic Person Collection
You can populate a collection like so:
Dim personColl as New Collection("PERSON", Nothing, False, False)
Call personColl.add(createPerson("Dennis", "Doe", 84))
Call personColl.add(createPerson("Denise", "Doe", 82))
Call personColl.add(createPerson("Andrew", "Doe", 65))
Call personColl.add(createPerson("Fred", "Doe", 65))
Call personColl.add(createPerson("Frances", "Doe", 67))
Call personColl.add(createPerson("John", "Doe", 42))
Call personColl.add(createPerson("Jane", "Doe", 30))
Call personColl.add(createPerson("Johnathan", "Doe", 12))
Call personColl.add(createPerson("Janet", "Doe", 6))
Call personColl.add(createPerson("Ken", "Doe", 41))
Call personColl.add(createPerson("Karen", "Doe", 39))
Call personColl.add(createPerson("Bill", "Doe", 19))
Call personColl.add(createPerson("Bob", "Doe", 17))
Call personColl.add(createPerson("Ben", "Doe", 10))
On the first line, you create a Collection that will only accept Person
objects, but will be non-unique and unsorted.
Imagine the feed of data you have is based on a family tree format: Dennis and Denise had twins - William and Fred; William did not marry, but Fred married Frances and had two sons, John and Ken; John married Jane and had two children; Ken married Karen and had three children.
You add each Person object in turn to the Collection. For brevity of code, you do this using a function:
Function createPerson(firstName as String, lastName as String, age as Integer) as Person
Set createPerson = new Person()
createPerson.firstName = firstName
createPerson.lastName = lastName
createPerson.age = age ' (1)
End Function
- The Person object, assigned to a variable with the same name as the function, is implicitly returned at the end of the function.
Filter and Comparator
You now have a Collection of Person objects based on insertion order. It may be that our code needs to perform some function with those or generate some reporting. But imagine at some point in the processing, you then want to filter out those Person entries with an age greater of 80 or more, and sort the rest on age, then first name.
PersonFilter
To filter out entries, you extend the CollectionFilter class with a PersonFilter class. The CollectionFilter class has an empty constructor, so you don't need to specify the New method. But you do want to override the filter()
method. In this case, you want to return true only if the age is less than 80.
Class PersonFilter as CollectionFilter
Function filter(source as Variant) as Boolean
Return CInt(source.age) < 80
End Function
End Class
PersonComparator
The sort options for a Collection can't be changed once it's created, and the filter()
method uses the same settings as the Collection it's filtering. But you can add the entries to a new Collection that's sorted. To sort, you need to create a custom Comparator.
This can be done with:
The Comparator class doesn't have an empty constructor, so you need to explicitly add the constructor, from line 3. However, both constructors have the same arguments, so the code is very simple and it will automatically call the Comparator
class's New
method.
From line 7, you override the compare
method. This method takes two parameters - source
, the value being inserted, and target
, the value it's being compared to. On lines 12 and 13, you explicitly cast source and target as Person objects. This means the runtime automatically understands the data type of the properties, so you don't need to explicitly convert to doubles or strings.
If the ages are different, it's simple. On lines 14 and 15, we return -1 ("before") if the age on the source Person object is less than the target Person object. On lines 16 and 17, you return 1 ("after") if the age of the source Person object is greater than the target Person object. The compare
method is used for uniqueness as well as sort order, so you can't just return 0 in all other cases. you need to check the first name and, if that's still 0, check the last name.
Because we're going to use the Comparator for a unique collection, we need to override the equals()
function to return True
if we find an exact match. The compare()
function returns an integer where 0 means it's an exact match, so we can reuse that function.
Passing to Sorted Collection
Now you are ready to filter the Collection and pass it to a sorted Collection.
Note
Running a filter iterates the source Collection and returns a new Collection. Adding the resulting (filtered) Collection to a new sorted Collection also requires iterating the Collection. For a small Collection, the performance overhead will be minimal and there may be benefits for debugging in having multiple steps.
On line 1, you create an instance of the PersonFilter. On line 2 you create an instance of the PersonComparator, telling it to sort in ascending order (youngest to oldest, then first name A-Z). On line 3, you create a sorted, non-unique Collection passing in the PersonComparator for sorting and identifying uniques.
Finally, on line 5, you perform the process. personColl.filter(filter)
is called first, returning a filtered Collection. The resulting Collection is then passed to Call sortedPersonColl.addAll()
.
Transformer and Custom Collection
Imagine you want to output summary data about the people, hiding their actual age but just defining age groups - child, young person, adult, pensioner. Imagine the code already has a class for Person information in this format, like so:
A CollectionTransformer can convert from one class to the other.
PersonTransformer
You just need to create a PersonTransformer extending the CollectionTransformer class, like so:
The CollectionTransformer has an empty constructor, so we don't need to include the Sub New
. We just need to extend the transform
function, from line 3. This takes an incoming value as a Variant (source
) and returns the transformed value as a Variant.
For readability, you create a newPerson
on line 4 using the PersonAlt
class you need to output. And you declare an oldPerson
object on line 5 as the Person
class you expect incoming. On line 7, you explicitly cast source
into the oldPerson
object.
On line 8, you set the fullName
property.
From line 9, you have a Select Case
statement to convert age to a textual range value. Lines 10 and 14 use Is
with a comparison operator, to identify children or pensioners. Line 12 uses a range expression, which is inclusive, to pick up values from and including 16 through to and including 30. Line 16 then picks up all remaining ages. The ageRange
variable in newPerson
is set accordingly. Finally, on line 19, you return the newPerson object.
PersonAltCollection
You could just use a normal Collection class, extracting the instance of PersonAlt
using getNthElementRaw()
. But to show full functionality, you will create a custom Collection class:
Class PersonAltCollection as Collection
Sub New(), Collection("PERSONALT", Nothing, False, False)
' Nothing to do
End Sub
Function getNthElement(index as Long) as PersonAlt
Return getNthElementRaw(index)
End Function
End Class
The constructor from line 3 is unusual. The incoming constructor takes no arguments, but the constructor of the base Collection
class requires arguments. The correct syntax for this is Sub
plus incoming constructor New()
, then a comma and the base class name followed by the arguments to pass , Collection("PERSONALT", Nothing, False, False)
. The constructor doesn't need to do anything else, so there is no additional code.
You then add a getNthElement()
function from line 7, which will explicitly return an instance of the PersonAlt
class. Because you're returning an object, you need the Set
keyword, then you just need to return the value from the base class's getNthElementRaw()
function.
Note
Because you're calling a different method in the base class, you can just use its name. There may be scenarios where you want to overload a method from the base class, such as add()
, to run some code and then call the base class's add()
function. This can be done using baseClassName..methodName, i.e. Call Collection..add(source)
Performing the Transformation
Note
The transform
function also iterates the source Collection and adds elements to the new target Collection. If the target Collection is sorted, it will still use a Comparator to sort the incoming value against the values already in the target Collection.
You perform the transformation like so:
Dim personAltTransformer as New PersonTransformer()
Dim personAltColl as New PersonAltCollection()
Call sortedPersonColl.transform(personAltTransformer, personAltColl)
You create an instance of the PersonTransformer
and an instance of the PersonAltCollection
class. Then we pass these into the transform
function of the resulting sortedPersonColl
from the filter and sort. Because addAll()
is a fluent function, we can perform the whole thing in a single line: Call sortedPersonColl.addAll(personColl.filter(filter)).transform(personAltTransformer, personAltColl)
.
Printing out the Result
Finally, you can print out the result like so:
Dim i as Long
Dim altPerson as PersonAlt
Do
Set altPerson = personAltColl.getNthElement(i)
Print altPerson.fullName & " - " & altPerson.ageRange
Loop While ++i < personAltColl.elementCount
After declaring variables you use Do...Loop While
to iterate the Collection and print out the full name and age range.
Note
There are quite a few iterations of the initial Collection here. The code could be streamlined, if nothing more than the incoming Collection and outgoing printout were required. But the example shows using the broad variety of the Collection class functionality.