Results 1 to 14 of 14

Thread: How to work with a List of Structures

  1. #1

    Thread Starter
    New Member
    Join Date
    Dec 2016
    Posts
    10

    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

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,988

    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

  3. #3
    Smooth Moperator techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,532

    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
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  4. #4

    Thread Starter
    New Member
    Join Date
    Dec 2016
    Posts
    10

    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!

  5. #5
    PowerPoster SJWhiteley's Avatar
    Join Date
    Feb 2009
    Location
    South of the Mason-Dixon Line
    Posts
    2,256

    Re: How to work with a List of Structures

    Quote Originally Posted by techgnome View Post
    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."

  6. #6
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,988

    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

  7. #7
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,754

    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
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

  8. #8
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,988

    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

  9. #9
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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:

    1. Derive a new class from List(Of T) and override the methods like IndexOf() to do what you want.
    2. Make your type implement IComparable(Of T) and probably also IEquatable(Of T).
    3. Create an IComparer(Of T) for your type.
    4. 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.

  10. #10
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,754

    Re: How to work with a List of Structures

    Quote Originally Posted by Shaggy Hiker View Post
    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.
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

  11. #11
    Smooth Moperator techgnome's Avatar
    Join Date
    May 2002
    Posts
    34,532

    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
    * I don't respond to private (PM) requests for help. It's not conducive to the general learning of others.*
    * I also don't respond to friend requests. Save a few bits and don't bother. I'll just end up rejecting anyways.*
    * How to get EFFECTIVE help: The Hitchhiker's Guide to Getting Help at VBF - Removing eels from your hovercraft *
    * How to Use Parameters * Create Disconnected ADO Recordset Clones * Set your VB6 ActiveX Compatibility * Get rid of those pesky VB Line Numbers * I swear I saved my data, where'd it run off to??? *

  12. #12

    Thread Starter
    New Member
    Join Date
    Dec 2016
    Posts
    10

    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

  13. #13
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    38,988

    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

  14. #14
    Powered By Medtronic dbasnett's Avatar
    Join Date
    Dec 2007
    Location
    Jefferson City, MO
    Posts
    9,754

    Re: How to work with a List of Structures

    Quote Originally Posted by BillMc View Post
    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
    My First Computer -- Documentation Link (RT?M) -- Using the Debugger -- Prime Number Sieve
    Counting Bits -- Subnet Calculator -- UI Guidelines -- >> SerialPort Answer <<

    "Those who use Application.DoEvents have no idea what it does and those who know what it does never use it." John Wein

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
  •  



Click Here to Expand Forum to Full Width