-
Feb 5th, 2017, 01:01 PM
#1
Thread Starter
New Member
How to work with a List of Structures
My question has to do with Lists but more exactly, a List of Structures.
I know how to create, add to, sort and index a list of one piece of data. Here's an example using a data type of Date:
Dim index As Integer
'define a list of type Date
Dim MyList As New List(Of Date) '<----define
'add items to the list
MyList.Add(#4/18/2017#) '<----add
MyList.Add(#8/3/2015#)
MyList.Add(#6/27/2016#)
'sort the list
MyList.Sort() '<----sort
'find items in the list
index = MyList.IndexOf(#6/27/2016#) '<----index
But how can I do the same for a list that is composed of multiple pieces of data? This is what I tried:
I defined a Structure named StrucName which is comprised of two pieces of data:
Public Structure StrucName
Public dte As Date
Public name As String
End Structure
I then created a list of data type StrucName:
Dim MyList As New List(Of StrucName)
Then I tried to add data to the list like this:
MyList.dte.add(#4/18/2017#)
Got error: 'dte' not a member of 'Systems.Collections.Generic.List(Of WindowsApplication2.Form1.ListStructure'
Also, when trying to index the list like this:
Dim index As Integer = MyList.dte.IndexOf(#6/27/2016#)
I received the same error.
Would appreciate help.
Thanks
-
Feb 5th, 2017, 01:53 PM
#2
Re: How to work with a List of Structures
You are creating a list of the structures, not a list of the members of the structures. Adding a structure is actually a bit more complicated to explain, because there are a variety of ways you might go about it. The easiest way would be this:
MyList.Add(New StrucName)
MyList.Add(New StrucName)
Then, to reference an item, it would be the same way that you would reference an item in any other list, except that what you are getting is the item from the list, which has members:
MyList(0).dte
MyList(0).name
However, things like IndexOf simply won't work the same way as they would for a list of a simpler type. After all, what is the index of a structure that happens to have some date? The structure isn't the date, and there is certainly no way for the compiler to take a date argument in IndexOf and know that you want to find the first structure that has that date. So, IndexOf becomes essentially useless. It does something, it just isn't going to do something useful in almost every case. You can implement some interfaces on your structure to make it more useful, but that's often not worth the effort. The only thing that IndexOf does is iterate through the items looking for the first item that matches the argument, and you can do that yourself without any great difficulty.
My usual boring signature: Nothing
-
Feb 5th, 2017, 08:50 PM
#3
Re: How to work with a List of Structures
So, IndexOf becomes essentially useless. It does something, it just isn't going to do something useful in almost every case. You can implement some interfaces on your structure to make it more useful, but that's often not worth the effort. The only thing that IndexOf does is iterate through the items looking for the first item that matches the argument, and you can do that yourself without any great difficulty.
What IndexOf does is the same, no matter what's passed to it ... it returns the index where that value is found in the list. In the case of a list of structures, that value is the pointer to where that instance of the structure exists. It's the value of the pointer to that instance of the structure... not the value or any values within the structure... Which is why Shaggy says it's essentially useless... because two instances of the structure won't occupy the same address location - even if they have the same values for the structure fields.
-tg
-
Feb 5th, 2017, 11:54 PM
#4
Thread Starter
New Member
Re: How to work with a List of Structures
Thank you Shaggy and Techgnome.
I think I understand how to create, add to and retrieve from a List of Structures.
And I understand I'll have to write my own IndexOf and Sort routines.
Thank you very much!
-
Feb 6th, 2017, 07:49 AM
#5
Re: How to work with a List of Structures
Originally Posted by techgnome
What IndexOf does is the same, no matter what's passed to it ... it returns the index where that value is found in the list. In the case of a list of structures, that value is the pointer to where that instance of the structure exists. It's the value of the pointer to that instance of the structure... not the value or any values within the structure... Which is why Shaggy says it's essentially useless... because two instances of the structure won't occupy the same address location - even if they have the same values for the structure fields.
-tg
This isn't necessarily true, and why structures can be more complex to use than classes:
Code:
Private Structure MyStruct
Public Value As Integer
Public Name As String
End Structure
Code:
Dim s As New List(Of MyStruct)
s.Add(New MyStruct With {.Value = 1})
s.Add(New MyStruct With {.Value = 5})
s.Add(New MyStruct With {.Value = 2})
s.Add(New MyStruct With {.Value = 3, .Name = "three"})
s.Add(New MyStruct With {.Value = 7})
s.Add(New MyStruct With {.Value = 0})
Dim i As Integer = s.IndexOf(New MyStruct With {.Value = 3, .Name = "three"})
Debug.WriteLine(i)
This returns an expected index.
"Ok, my response to that is pending a Google search" - Bucky Katt.
"There are two types of people in the world: Those who can extrapolate from incomplete data sets." - Unk.
"Before you can 'think outside the box' you need to understand where the box is."
-
Feb 6th, 2017, 10:25 AM
#6
Re: How to work with a List of Structures
Yeah, I so rarely use structures that I knew there could be nuances to structures in a list that I wasn't aware of.
In any case: You don't need to write your own sort. If you have a class or structure, you can implement the IComparable(of T) interface, then just call Sort(). Implementing the interface is trivial, too, but it is specific to how you want to do the sort. You basically have to provide a method (you get a stub of the method when you implement the interface) that takes an instance of the object and tells you whether it should be sorted above or below the current instance. That's usually really simple.
IndexOf is no loss. It doesn't do anything special, and isn't all that useful, anyways.
My usual boring signature: Nothing
-
Feb 6th, 2017, 11:15 AM
#7
Re: How to work with a List of Structures
I tried this and it produces the correct results, so I must be missing something.
Code:
Private Structure MyStruct
Public Value As Integer
Public theDate As DateTime
End Structure
Private myListOfStruct As New List(Of MyStruct)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For x As Integer = 5 To 1 Step -1
myListOfStruct.Add(New MyStruct With {.Value = x, .theDate = DateTime.Now.AddDays(x)})
Next
Dim idx As Integer = myListOfStruct.FindIndex(Function(ms) ms.theDate.Date = DateTime.Now.AddDays(4).Date)
Debug.WriteLine(myListOfStruct(idx).theDate)
'now sort
myListOfStruct = myListOfStruct.OrderBy(Function(ms) ms.theDate).ToList
'then the index again
idx = myListOfStruct.FindIndex(Function(ms) ms.theDate.Date = DateTime.Now.AddDays(4).Date)
Debug.WriteLine(myListOfStruct(idx).theDate)
End Sub
-
Feb 6th, 2017, 01:12 PM
#8
Re: How to work with a List of Structures
Well, I'm certainly missing something: What problem is that a solution to? I see that you're doing a sort, and finding an index, but it's not a case of blindly calling Sort, nor does it use IndexOf? So, it seems to confirm the point that you can both sort and find an element, just not necessarily without a bit more thought than a simpler list would require.
My usual boring signature: Nothing
-
Feb 6th, 2017, 03:23 PM
#9
Re: How to work with a List of Structures
Here's the deal.
IndexOf() needs to know how to answer the question, "Are these two things equal?". Sort() needs to know how to answer, "How do these two things compare?", which is a more complicated case of "Are these two things equal?".
When you have a list of primitive types like Integer, it "just works", because those types contain all the information needed to answer those two questions. When you make your own class or structure, things get more complicated. Let's just call "a class or structure" by the name "a type", so I don't have to type so much.
For classes, the default behavior for, "Are these two things equal?" is to use the answer to, "Are these two variables referencing the same thing?" That is to say, you can make two Employee objects with the name "Alice" and the same salary, but they will not be considered equal because they are the same instances. Structures behave similarly, please don't make me write the multiple pages that go over the nitpicky differences. The important part: .NET has no clue how to compare custom types to each other, the burden falls on you.
So when you make a custom type, if you want to use some of the nicer List methods, you're going to have to write a bit of code to explain how to compare your type. In .NET we have three options. You can:
- Derive a new class from List(Of T) and override the methods like IndexOf() to do what you want.
- Make your type implement IComparable(Of T) and probably also IEquatable(Of T).
- Create an IComparer(Of T) for your type.
- Use comparer delegates.
That list is in order of (in my opinion) hardest to easiest. I like to use comparer delegates wherever I can. Don't worry about it too much if you don't know what that means, I'm going to demonstrate. Sometimes, you can't use comparer delegates. When that happens, I prefer to use an IComparer(Of T). It is very rare that I have to implement (1) or (2), and I try to find ways to avoid them.
So let's make a simple class, we'll go with an Employee. Everything I'm going to do can apply to Structures, but do keep in mind that in .NET, 99.9999% of types should be Classes, not Structures. Structures are much harder to write correctly, but that's a different topic. Let's stay on topic. Here's what Employee looks like:
Code:
Public Class Employee
Public Name As String
Public Salary As Integer
End Class
Supporting Sort()
This is the easiest place to start. If you look at the documentation for List.Sort(), you'll find overloads that take either an IComparer(Of T) or a Comparison(Of T). The IComparer(Of T) represents implementation (3). Comparison(Of T) is a delegate, thus an implementation of (4). Let's talk about it.
Comparison(Of T) is a function that takes two objects and returns an Integer. The Integer should be less than zero if the first argument is "less than" the second argument, zero if the two objects are equal, and greater than zero if the first argument is "greater than" the second argument. So if we wanted to sort a list of Employee by name, we could write:
Code:
Dim employees As New List(Of Employee)()
employees.Add(New Employee() With { .Name = "Alice", .Salary = 31700 })
employees.Add(New Employee() With { .Name = "James", .Salary = 32500 })
employees.Add(New Employee() With { .Name = "Bob", .Salary = 31500 })
employees.Sort(Function(l, r) l.Name.CompareTo(r.Name))
For Each e As Employee in employees
Console.WriteLine("{0,-8} {1}", e.Name, e.Salary)
Next
What's happening here? Maybe you have or haven't used "lambda methods". You need to be using them. The important line calls Sort(). If you've seen a lambda method before, you're saying, "Oh, clever!" If not, you need the next paragraph.
Comparison(Of T) is defined: "Function Comparison(Of T)(ByVal left As T, ByVal right As T) As Integer". That means Sort() expects, as a parameter, a function that takes two arguments of the same type and returns an Integer. I could have written a function like that and used a Delegate to get there, but the syntax I used is shorthand. As you can see, it's a Function, it takes two parameters, and the value it returns is an Integer. There's a special rule that a lambda function on one line like this doesn't have to use the "Return" keyword, I think it would've been more in line with VB syntax to require it anyway. C'est la vie.
If your mind is really boggled, this is how we would have written this in VB 2008 and earlier:
Code:
Public Sub Main()
Dim employees As New List(Of Employee)()
employees.Add(New Employee() With { .Name = "Alice", .Salary = 31700 })
employees.Add(New Employee() With { .Name = "James", .Salary = 32500 })
employees.Add(New Employee() With { .Name = "Bob", .Salary = 31500 })
employees.Sort(New Comparison(Of Employee)(AddressOf CompareEmployees))
For Each e As Employee in employees
Console.WriteLine("{0,-8} {1}", e.Name, e.Salary)
Next
End Sub
Function CompareEmployees(ByVal left As Employee, ByVal right As Employee) As Integer
return left.Name.CompareTo(right)
End Function
It's more verbose, but not terrible.
Why do I like (4)? Because it's very flexible, and you can on-the-fly change your parameters. What if I wanted to sort by salary? This is how I'd change the Sort() line in my lambda example:
Code:
employees.Sort(Function(l, r) l.Salary.CompareTo(r.Salary))
What if I want something really complex, like, "Sort by salary, but if salaries are equal, sort by name?" You can write multi-line lambdas, but if you do so you can't omit the Return keyword:
Code:
employees.Sort(Function(l, r)
If (l.Salary = r.Salary) Then
Return l.Name.CompareTo(r.Name)
Else
Return l.Salary.CompareTo(r.Salary)
End If
End Function)
The more complex that gets, the more I lean towards implementation (3). How's that work?
Well, IComparer(Of T) is an interface that requires a method that does basically what Comparison(Of T) did. So you need to define a class that implements it, and generally one implementation of the class per kind of sorting you want to do. What do I mean? Well, by name:
Code:
Public Sub Main()
Dim employees As New List(Of Employee)()
employees.Add(New Employee() With { .Name = "Alice", .Salary = 31700 })
employees.Add(New Employee() With { .Name = "James", .Salary = 32500 })
employees.Add(New Employee() With { .Name = "Bob", .Salary = 31500 })
employees.Sort(New EmployeeByNameComparer())
For Each e As Employee in employees
Console.WriteLine("{0,-8} {1}", e.Name, e.Salary)
Next
End Sub
Public Class EmployeeByNameComparer
Implements IComparer(Of Employee)
Public Function Compare(ByVal left As Employee, ByVal right As Employee) As Integer Implements IComparer(Of Employee).Compare
Return left.Name.CompareTo(right.Name)
End Function
End Class
If you look close, it's all familiar code. It's just a little more trouble to make the interface implmentation. That's why I use (4) if I can.
IndexOf()
Technically, you can't make IndexOf() work without using technique (1). But you can get the same thing. Let's say you care about finding the index. The List.Find() method returns the object instance that matches a predicate for a particular value. What's a predicate? Mathematically, it's "any function that returns a Boolean". In the case of Find(), it will pass you each item in the list, and expect you to return True or False based on some criteria you decide. It's easier to demonstrate. Let's say we're looking for an employee named "James":
Code:
Dim james = employees.Find(Function(e) e.Name = "James")
That will either return the first Employee with the name "James", or Nothing if no matches are found. This is step 1 to getting the index, just in case you want that. Note this is also the core of answering questions like "Does the list contain an employee named "James"?"
If we want the index, we can pass that object to IndexOf()! Because it returns us exactly the object that's in the list, we know it's a valid parameter to IndexOf():
Code:
Dim james = employees.Find(Function(e) e.Name = "James")
If james IsNot Nothing Then
Dim jamesIndex = employees.IndexOf(james)
Console.WriteLine(jamesIndex)
End If
But speaking honestly, you were probably using IndexOf() to accomplish what Find() does.
It's easier than it looks, I promise. Tinker with the example code.
dbasnett is scratching at the surface of something more profound and useful, but decided to let the code do the talking with no explanation:
LINQ already has these tools.
LINQ's some fancy methods that work on EVERY collection type that follows certain rules. It's very useful at getting out of some tight spots. And like any power tool, it can get you into trouble you couldn't imagine without it. Without going into too much detail, LINQ tends to "transform" collections rather than change them. Let me demonstrate with a LINQ sorting example:
Code:
Dim sortedEmployees As IEnumerable(Of Employee) = employees.OrderBy(Function(l, r) l.Name.CompareTo(r.Name))
'sortedEmployees' will be an IEnumerable collection that is the result of sorting 'employees'. What's IEnumerable? There's whole LINQ tutorials about it, but it's sort of like a list you can't index. That probably sounds useless. It's hard to make examples of exactly how useful it is. It's intended to be composed more easily than plain old list methods.
For example, this is LINQ for "Find me the highest-paid employee named "James"":
Code:
Dim highestPaidJames = employees.Where(Function(e) e.Name = "James")
.OrderByDescending(Function(l, r) l.Salary.CompareTo(r.Salary))
.First()
Confused yet? Don't feel bad. I ignored LINQ for a year, and it took at least that long for me to come to grips with it. Find some tutorials and start small.
-
Feb 6th, 2017, 04:53 PM
#10
Re: How to work with a List of Structures
Originally Posted by Shaggy Hiker
Well, I'm certainly missing something: What problem is that a solution to? I see that you're doing a sort, and finding an index, but it's not a case of blindly calling Sort, nor does it use IndexOf? So, it seems to confirm the point that you can both sort and find an element, just not necessarily without a bit more thought than a simpler list would require.
The OP, it seemed to me, was using the wrong tool for the job. Using a list of some datatype makes using IndexOf a solution, but when the list is a structure or class IndexOf might not fit the bill.
-
Feb 6th, 2017, 04:57 PM
#11
Re: How to work with a List of Structures
To be fair, the REAL problem was they weren't using IndexOf correctly... that was the real problem. They packed the list with complex data, then tried to use a just the date value to extract out the whole structure... it doesn't work like that. It's possible that a list isn't what should be used, but maybe a Dictionary with the date as the key and the structure as the value... I don't know... but the bottom line is if you have this:
Dim MyList As New List(Of StrucName)
this use of IndexOf isn't going to work:
Dim index As Integer = MyList.dte.IndexOf(#6/27/2016#)
-tg
-
Feb 6th, 2017, 05:08 PM
#12
Thread Starter
New Member
Re: How to work with a List of Structures
WOW, I never expected people to take so much time to help me so I truly appreciate everyone's responses here.
But, if you don't mind, I'd like to ask a different, but related, question.
What I'm trying to do is store internal data, where each data record is composed of a date and several pieces of other data e.g. name, address, etc. The date is the primary field and will be used for indexing and sorting records.
I assumed the best way to do that was to create a Structure like this:
Public Structure StrucName
Public dte As Date
Public name As String
Public address as String
End Structure
then store each data record in a List.
With the answers you've provided, I think I can make that work (thank you very much).
But is there a better, more elegant way of doing this instead of using Structures?
Thanks
-
Feb 6th, 2017, 06:21 PM
#13
Re: How to work with a List of Structures
How about a Datatable? You've pretty much described tabular data, and if you want to be searching, then a datatable has some excellent extension methods to assist you with that, such as Select.
A structure or a class is also pretty reasonable in this case, and the way to decide may have more to do with how you want to store the information, and somewhat on how you want to display it. A datatable will bind very easily to a control like a DataGridView. That's a one line bind, so you don't get any simpler.
The big thing is that you can write a datatable to XML, or read it from XML, so you can store/retrieve the data with a single WriteXML/ReadXML call. You could serialize a list of structures, too, but it would take a few more lines than the one that is needed for a datatable. Of course, XML is just text, so it can be easily read/edited with any text editor, so, if security is an issue for you, then XML is not ideal, in which case binary serialization might be better, which is likely done more easily with a List than a datatable, though it may actually be exactly the same amount of work for each.
My usual boring signature: Nothing
-
Feb 7th, 2017, 06:45 AM
#14
Re: How to work with a List of Structures
Originally Posted by BillMc
WOW, I never expected people to take so much time to help me so I truly appreciate everyone's responses here.
But, if you don't mind, I'd like to ask a different, but related, question.
What I'm trying to do is store internal data, where each data record is composed of a date and several pieces of other data e.g. name, address, etc. The date is the primary field and will be used for indexing and sorting records.
I assumed the best way to do that was to create a Structure like this:
Public Structure StrucName
Public dte As Date
Public name As String
Public address as String
End Structure
then store each data record in a List.
With the answers you've provided, I think I can make that work (thank you very much).
But is there a better, more elegant way of doing this instead of using Structures?
Thanks
Choosing between Class and Structure
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|