Searching ArrayList of Classes

P

Paul Nations

I've got arraylists of simple classes bound to controls. I need to search
through those arraylists to set the correct SelectedItem in the control.
The code looks like:

Public Class DegreeMaintenance
Private arrCipCodes As New ArrayList
'populate reader with data
With rdr
Do While .Read
arrCipCodes.Add(New CipCode(.GetString(0), .GetString(1)))
Loop
.Close()
End With
arrCipCodes.Sort()

Public ReadOnly Property CipCodes() As ArrayList
Get
Return arrCipCodes
End Get
End Property
End Class
'*****************************************************************
Public Class CipCode
Private myCipDisplay As String
Private myCipCode As String
Public Sub New(ByVal strCipDisplay As String, ByVal strCipCode As
String)
MyBase.new()
With Me
.myCipDisplay = strCipDisplay 'e.g. 01.9999 Agriculture,
Agriculture Operations and Related Sciences, Other
.myCipCode = strCipCode 'e.g. 01.9999
End With
End Sub
Public ReadOnly Property CipDisplay() As String
Get
Return myCipDisplay
End Get
End Property
Public ReadOnly Property CipCode() As String
Get
Return myCipCode
End Get
End Property
End Class
'****************************************************************
And finally in a form is this code:
'
Me.cbCipCode.SelectedIndex =
DegreeMaintenance.CipCodes.BinarySearch(dd.CipDisplay)

When I execute this line I get this message:
Additional information: Specified IComparer threw an exception.

I'm not sure where to go from here. Do I need to implement an IComparer?
How do I search on the second element of the class members of my arraylist?
 
L

Larry Lard

Paul said:
I've got arraylists of simple classes bound to controls. I need to search
through those arraylists to set the correct SelectedItem in the control.
The code looks like:

Public Class DegreeMaintenance
Private arrCipCodes As New ArrayList
'populate reader with data
With rdr
Do While .Read
arrCipCodes.Add(New CipCode(.GetString(0), ..GetString(1)))
Loop
.Close()
End With
arrCipCodes.Sort()

Public ReadOnly Property CipCodes() As ArrayList
Get
Return arrCipCodes
End Get
End Property
End Class
'*****************************************************************
Public Class CipCode
Private myCipDisplay As String
Private myCipCode As String
Public Sub New(ByVal strCipDisplay As String, ByVal strCipCode As
String)
MyBase.new()
With Me
.myCipDisplay = strCipDisplay 'e.g. 01.9999 Agriculture,
Agriculture Operations and Related Sciences, Other
.myCipCode = strCipCode 'e.g. 01.9999
End With
End Sub
Public ReadOnly Property CipDisplay() As String
Get
Return myCipDisplay
End Get
End Property
Public ReadOnly Property CipCode() As String
Get
Return myCipCode
End Get
End Property
End Class
'****************************************************************
And finally in a form is this code:
'
Me.cbCipCode.SelectedIndex =
DegreeMaintenance.CipCodes.BinarySearch(dd.CipDisplay)

When I execute this line I get this message:
Additional information: Specified IComparer threw an exception.

I'm not sure where to go from here. Do I need to implement an
IComparer?

Yes. BinarySearch needs to be able to compare the objects in the
ArrayList in order to search it, and it doesn't know how to compare
CipCode's.
How do I search on the second element of the class members of my
arraylist?

TWO WAYS to do this:

a) First method: Define a class that compares CipCode's and implements
IComparer; supply one of these to BinarySearch:

Public Class CipCodeComparer
Implements IComparer

Public Function Compare(ByVal x As Object, ByVal y As Object) As
Integer Implements System.Collections.IComparer.Compare
'because we don't type check here -
'it becomes our responsibility to ensure that a CipCodeComparer
'is only ever used to compare CipCode's
Dim cx As CipCode = DirectCast(x, CipCode)
Dim cy As CipCode = DirectCast(y, CipCode)

Return String.Compare(cx.CipCode, cy.CipCode)

End Function
End Class

Hopefully this will be self-explanatory - this is a class whose sole
purpose is to provide an implementation of IComparer that compares
CipCode's by their CipCode property. Note that I have taken the
stylistic decision to explicitly passthrough to String.Compare; I could
just as easily have said

if cx.cipcode < cy.cipcode then
return -1
elseif cx.cipcode > cy.cipcode then
return 1
else
return 0
end if

I think just passing through to String.Compare better shows what is
being done.

Anyway, once you've defined this class, you just need to supply it to
BinarySearch using the appropriate overload:

Me.cbCipCode.SelectedIndex =
DegreeMaintenance.CipCodes.BinarySearch(dd.CipDisplay, New
CipCodeComparer)

and you should be in business.

b) Second method: have CipCode implement IComparable, so that when the
default comparer (the one that gets used when no comparer is explicitly
mentioned in the call to BinarySearch) comes calling, comparisons will
be done as you want:

add to the definition of CipCode:
Implements IComparable

Public Function CompareTo(ByVal obj As Object) As Integer
Implements System.IComparable.CompareTo
Dim other As CipCode = DirectCast(obj, CipCode)
Return Me.CipCode.CompareTo(other.CipCode)
End Function

(as before, without type checking it's our responsibility to make sure
CipCode's are only ever compared to other CipCode's)

Now when you use the comparer-less overlod of BinarySearch, it will
know get a useful answer when it asks the CipCode's to compare amongst
themselves.

btw, be sure to check you're filling the ArrayList in Code order, as
BinarySearch assumes this.
 
P

Paul Nations

Larry;

I really appreciate the help. It's what I was trying to do before I got
desperate and posted to the newsgroup.

However, I've tried both methods you suggested and am still getting the
"Specified IComparer threw an exception." error.

I've put a break in the compare functions of both methods and the
execution never enters that function.

Reading the newsgroups, I read that I should be able to set a break in
the compare fns and see the code executing in there, but I never get
that far.

Thanks for any further consideration you can give to this.
 
L

Larry Lard

OK will look again later but just quickly to check that when you
implement IComparer, you are actually passing an instance to
BinarySearch? eg

Me.cbCipCode.SelectedIndex =
DegreeMaintenance.CipCodes.BinarySearch(dd.CipDisplay, New MyComparer)

Just checking the obvious stuff first :)
 
P

Paul Nations

Yes. Here's the line:

Me.cbCipCode.SelectedIndex =
cDegreeMaintenance.CipCodes.BinarySearch(dd.CipDisplay, New
CipCodeComparer)

Does the CipCodeComparer class need a Public Sub New or something
besides the Implements & Compare function? How does it get
instantiated?
 
P

Paul Nations

In all the examples from Dynamic Help where a comparer is built, the
comparer class is embedded within another class. Is this necessary? Can
the comparer class be a standalone? If not where would I embed the comparer
class, in the class of objects being compared or in the form calling the
comparison?

Thanks.
 
L

Larry Lard

Sorry for not coming back Paul.

You don't need to explicitly code a constructor for your comparer class
- remember that when you don't provide a constructor, VB implicitly
creates a 'null' constructor (that takes no arguments and does
nothing), so just doing New CipCodeComparer as you have is fine.

As to where to define the comparer, the general OO principle is that
you 'hide' it as much as you can, which is why examples often have
comparers defined as private (embedded) classes. If you're going to
have a comparer that is used across classes then you would need to
define it in a broader scope.

If you're still having trouble getting it to work, try stripping the
code down to a minimal not-working example then post that.
 
L

Larry Lard

Paul said:
I've stripped out everything that doesn't relate to the BinarySearch
problem I'm having. The source is in 3 files plus the data xml.

Everything is here:
http://www.arkansashighered.com/software/arraylistsearchproblem.zip

OK, what's happening is that you're actually searching the ArrayList of
CipCodes for a given CipCode based on the just the code, ie what you
want is:

Given the *code* and the collection, return the index of the object
with that code within the collection

whereas what BinarySearch does is

Given the *object* and the collection, return the index of that object
within that collection.


Probably the easiest way out of this is to note that when the main form
calls the edit for, at that point we *already* know the index of the
selected code within the list - so just make this one of the arguments
with which the edit form is created! - pass it through to
PopulateControls and set the combo box's index to it.


If you do later want the ability to get a CIP object given just the
code, look into using a Hashtable.
 
P

Paul Nations

OK. That sounds reasonable. I'll try that, since I'm already passing
in some other values in the Sub New.

Thanks for all your efforts.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top