1

I'm trying to achieve something where the answer is already given for. But it's in c# and I don't have any knowledge what-so-ever over c# so I'm looking for a vb.net alternative.

I made a class called BomItem which has several properties like quantity, description etc.

I add these BomItems into a List(of BomItem) but now I would like to sort them according to a property. How can you sort the items based on the ItemNumber property?

Here is the link to the c# solution I found.

My class code

Public Class BomItem
    Public Property ItemNumber As String
    Public Property Description As String
    Public Property Quantity As Double
    Public Property Material As String
    Public Property Certificate As String
End Class

How I add the BomRow objects

    _NewBomList.Add(New BomItem() With {
                    .ItemNumber = oRow.ItemNumber,
                    .Description = oPropSet.Item("Description").Value,
                    .Quantity = oRow.TotalQuantity,
                    .Material = oPropSet.Item("Material").Value,
                    .Certificate = CustomPropertySet.Item("Cert.").Value})

Comparer

Public Class NaturalSort

Implements IComparer

Public Function Compare(ByVal x As Object,
            ByVal y As Object) As Integer Implements IComparer.Compare

    ' [1] Validate the arguments.
    Dim s1 As String = x
    If s1 = Nothing Then
        Return 0
    End If

    Dim s2 As String = y
    If s2 = Nothing Then
        Return 0
    End If

    Dim len1 As Integer = s1.Length
    Dim len2 As Integer = s2.Length
    Dim marker1 As Integer = 0
    Dim marker2 As Integer = 0

    ' [2] Loop over both Strings.
    While marker1 < len1 And marker2 < len2

        ' [3] Get Chars.
        Dim ch1 As Char = s1(marker1)
        Dim ch2 As Char = s2(marker2)

        Dim space1(len1) As Char
        Dim loc1 As Integer = 0
        Dim space2(len2) As Char
        Dim loc2 As Integer = 0

        ' [4] Collect digits for String one.
        Do
            space1(loc1) = ch1
            loc1 += 1
            marker1 += 1

            If marker1 < len1 Then
                ch1 = s1(marker1)
            Else
                Exit Do
            End If
        Loop While Char.IsDigit(ch1) = Char.IsDigit(space1(0))

        ' [5] Collect digits for String two.
        Do
            space2(loc2) = ch2
            loc2 += 1
            marker2 += 1

            If marker2 < len2 Then
                ch2 = s2(marker2)
            Else
                Exit Do
            End If
        Loop While Char.IsDigit(ch2) = Char.IsDigit(space2(0))

        ' [6] Convert to Strings.
        Dim str1 = New String(space1)
        Dim str2 = New String(space2)

        ' [7] Parse Strings into Integers.
        Dim result As Integer
        If Char.IsDigit(space1(0)) And Char.IsDigit(space2(0)) Then
            Dim thisNumericChunk = Integer.Parse(str1)
            Dim thatNumericChunk = Integer.Parse(str2)
            result = thisNumericChunk.CompareTo(thatNumericChunk)
        Else
            result = str1.CompareTo(str2)
        End If

        ' [8] Return result if not equal.
        If Not result = 0 Then
            Return result
        End If
    End While

    ' [9] Compare lengths.
    Return len1 - len2

End Function

End Class
Community
  • 1
  • 1
Mech_Engineer
  • 535
  • 1
  • 19
  • 46

3 Answers3

5

Use LINQ OrderBy:

_NewBomList.OrderBy(Function(bi) bi.ItemNumber)

and for descending:

_NewBomList.OrderByDescending(Function(bi) bi.ItemNumber)

If you want a numeric order in your string you have to convert it to an integer first:

_NewBomList.OrderBy(Function(bi) Integer.Parse(bi.ItemNumber))

Edit:
To provide a custom IComparer for the OrderBy extension you have to create a class which implements IComparer(Of String) where String are your ItemNumbers to compare:

 Class ItemNumberComparer  
     Implements IComparer(Of String)

     Public Function Compare(String x, String y)
         Dim ix As String() = x.Split("."C)
         Dim iy As String() = y.Split("."C)

         Dim maxLen As Integer = Math.Max(ix.Length, iy.Length)
         For i As Integer = 0 To maxLen - 2
             If ix.Length >= i AndAlso iy.Length >= i Then
                If Integer.Parse(ix(i)) < Integer.Parse(iy(i)) Then
                   Return -1 'If x.i is LT y.i it must be smaller at all
                ElseIf Integer.Parse(ix(i)) > Integer.Parse(iy(i)) Then
                   Return 1 'If x.i is GT y.i it must be bigger all
                End If
             End If
         Next
         'This code is only executed if x and y differ at last number or have different ´number of dots
        If ix.Length = iy.Length Then
            Return Integer.Parse(ix(ix.Length - 1)).CompareTo(Integer.Parse(iy(iy.Length - 1))) 'Only last number differs
       Else
           Return ix.Length.CompareTo(iy.Length) 'The number with more dots is smaller
       End If

     End Function     
  End Class

Call syntax:

Dim comparer = new ItemNumberComparer()
_NewBomList.OrderByDescending(Function(bi) bi.ItemNumber, comparer)
Alex B.
  • 2,145
  • 1
  • 16
  • 24
  • Will the perform a natural sort or by the ascii model? 1,2,3,4,5,6,7,8,9,10 or 1,10,2,20,3,30 ... ? – Mech_Engineer Aug 17 '16 at 09:45
  • 1
    If you don´t provide any custom IComparer it will be sorted alphabetically (1,10,2,20,...) – Alex B. Aug 17 '16 at 09:49
  • 1
    If you want the items sorted numerically by text values then you have to convert the `String` values to numbers, e.g. `_NewBomList.OrderBy(Function(bi) CInt(bi.ItemNumber))`. The thing is, if your values represent numbers then why are you storing them in a String property to begin with? – jmcilhinney Aug 17 '16 at 09:51
  • My last comment is not 100% correct: You can convert the string into an number (e.g. Integer) to achieve the same result without a custom IComparer. – Alex B. Aug 17 '16 at 09:52
  • IF I'm to use a IComparer ( which I would need ) how would this be written info this code? item numbers are 1.1, 1.2, 2.1, 2.2, 2.3, .. so a conversion to Integer will not be possible because of the `.` seperator in the item number. – Mech_Engineer Aug 17 '16 at 09:56
  • Wow, if only there was a numeric type that allowed you to have a dot in the number. Someone should really get on that. /s Seriously though, just use `Double` or `Decimal` instead of `Integer`. Also, please provide all the relevant information up front instead of drip-feeding. – jmcilhinney Aug 17 '16 at 10:00
  • Sorry for the lack of information. But it's not a decimal number I work with a drawing program its Bill of material and the itemnumbers are provided as string. for example 1 = a complete bottle, 1.1 is the bottle itself, 1.2 is the cap, 1.3 is the sticker, 1.4 is the liquid. but the program allows you to go deeper 1.2.3.8 could be a valid itemnumber for example. – Mech_Engineer Aug 17 '16 at 10:04
  • Should `1.1.1` be greater or less than `1.2` ? – Alex B. Aug 17 '16 at 10:36
  • less but I already have a natural comparer from code the project that does this. I added into my question. – Mech_Engineer Aug 17 '16 at 10:58
  • I never said it was not working. I asked if it was required and how to implement it. And that has been perfectly answered by you. Thank you! – Mech_Engineer Aug 17 '16 at 11:07
  • 1
    Ok fair comment ;) I implemented an alternative natural comparer with fewer lines than your example. Maybe it´s also usable for you. – Alex B. Aug 17 '16 at 11:15
  • If this method don't work for you , you just have to `_NewBomList = _NewBomList.OrderBy(Function(bi) bi.ItemNumber).ToList` It happened to me , lol – Anes Hamdani Feb 21 '21 at 11:23
2

This C# code from that other thread:

List<Order> SortedList = objListOrder.OrderBy(o=>o.OrderDate).ToList();

equates to this VB code:

List(Of Order) SortedList = objListOrder.OrderBy(Function(o) o.OrderDate).ToList()

As you can see, very little changes. You just have to know the syntax for generics and lambda expressions.

You should be aware, though, that this code does NOT sort your list. It sorts the items in the list and then adds them to a new list in that order. This is perfectly fine for many applications but if you're using that same list elsewhere then you won't see the new order there. While there are a few variations, one way to actually sort the list in place is like this:

objListOrder.Sort(Function(o1, o2) o1.OrderDate.CompareTo(o2.OrderDate))
jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
0

Another solution would be to implement the IComparable (see MSDN ref) interface. This interface is designed to sort your objects on a custom way :

Public Class BomItem
    Implements IComparable

    Public Property ItemNumber As String
    Public Property Description As String
    Public Property Quantity As Double
    Public Property Material As String
    Public Property Certificate As String

    Public Function CompareTo(obj As Object) As Integer
         Dim bom = CType(obj, BomItem)

         If Not bom Is Nothing then
             Return Me.ItemNumber.CompareTo(bom.ItemNumber)
         Else
             Throw New ArgumentException("Object is not a BomItem")
         End If
End Class

and you can sort the list this way :

Dim myList As New List(Of BomItem)

'Code to add your BomItems

myList.Sort()

This will actually sort your list, it does not create a new list.

Martin Verjans
  • 4,675
  • 1
  • 21
  • 48