Malcolm Hutcheon
Malcolm Hutcheon

Reputation: 3

Creating and sorting an array/arraylist/list

Currently, I have a List(of String) which contains data similar to:

"207.5,1"
"373,2"
"278.5,3"
"134,4"
"277,5"
"674,7"
"58.5,9"

To this list, I apply the two commands 'list.Sort' then 'list.Reverse' which both do exactly as expected, my List then contains:

"674,7"
"58.5,9"
"373,2"
"278.5,3"
"277,5"
"207.5,1"
"134,4"

As you can see, for all intents and purposes, this has worked perfectly, BUT, the sharp-eyed will notice that the entry "58.5,9" is out of place and should be at the bottom of the list.

I appreciate I am sorting strings here, so I'm bound to fail. What I need to discover please, is how can I copy the contents of the strings per line into another sortable container, which stores my numbers and 'indexes' as integers and/or singles? Ideally, I'll end up with an array or whatever of data like this:

674.0,7
373.0,2
278.5,3
277.0,5
207.5,1
134.0,4
58.5,9

I have tried as many iterations as I can think of (fairly new to this so probably missing the very obvious!). Please help if you can! Thanks.

Upvotes: 0

Views: 74

Answers (4)

The simplest way, at least in terms of lines of code, is to use the Sort overload which allows a Comparer. However, if you will be frequently using the numeric value(s) for these things, you should consider a class or structure for the data.

can I copy the contents of the strings per line into another sortable container

The problem is not the container, but the data. Strings of numerals do not sort by the numeric value.

Private Function ThingsCompare(x As String, y As String) As Int32
    Dim xVal As Double = Convert.ToDouble(x.Split(","c)(0))
    Dim yVal As Double = Convert.ToDouble(y.Split(","c)(0))

    If xVal < yVal Then Return -1
    If yVal < xVal Then Return 1

    ' equal, so compare segment 2
    Dim xVal2 As Double = Convert.ToDouble(x.Split(","c)(1))
    Dim yVal2 As Double = Convert.ToDouble(y.Split(","c)(1))

    If xVal2 < yVal2 Then Return -1
    If yVal2 < xVal2 Then Return 1

    Return 0
End Function

Usage:

things.Sort(AddressOf ThingsCompare)

Given the following data:

{"207.5,1", "373,2", "278.5,3", "9.1,1",
"9.1,9", "134,4", "277,5", "674,7", "58.5,9"}

(I added the "9" elements because as characters/numerals they will sort higher than all the others). Results:

9.1,1
9.1,9
58.5,9
134,4
207.5,1
277,5
278.5,3
373,2
674,7

Upvotes: 1

djv
djv

Reputation: 15774

Using LINQ and Lambda expressions

Dim input = {
    "207.5,1",
    "373,2",
    "278.5,3",
    "134,4",
    "277,5",
    "674,7",
    "58.5,9"
}

' orderedResult is a IOrderedEnumerable(Of String)
Dim orderedResult = input.
    OrderByDescending(Function(item) CDec(item.Split(","c)(0)))

' dictResult is a Dictionary(Of Integer, Decimal) 
' based on the sorted result
Dim dictResult = orderedResult.ToDictionary(
    Function(item) CInt(item.Split(","c)(1)),
    Function(item) CDec(item.Split(","c)(0)))

Upvotes: 1

Andrew Morton
Andrew Morton

Reputation: 25013

If you're going to be using the data more than once then it makes sense to have a class which represents it. That way you can give the parts meaningful names, have an easy way to create a new item, easily manipulate the data, and have your own way of converting it to a string.

It may look like a load of fiddly code, but you only have to write it once and then your life is much simpler when you want to use the data:

Option Infer On
Option Strict On

Module Module1

    Public Class Datum
        Property Number As Decimal
        Property Index As Integer

        Sub New()
            ' default constructor
        End Sub

        Sub New(NumberIndex As String)
            Dim parts = NumberIndex.Split(","c)
            ' a simple parameter check
            If parts.Length <> 2 Then
                Throw New ArgumentException("No comma found in " & NameOf(NumberIndex))
            End If

            Number = CDec(parts(0))
            Index = CInt(parts(1))

        End Sub

        Public Overrides Function ToString() As String
            Return $"{Number},{Index}"

        End Function

    End Class

    Sub Main()
        Dim myList As New List(Of String) From {"207.5,1", "373,2", "278.5,3", "134,4", "277,5", "674,7", "58.5,9"}
        Dim myData = (myList.Select(Function(d) New Datum(d))).ToList()

        Dim dataDescending = myData.OrderByDescending(Function(d) d.Number).ToList()

        Console.WriteLine(String.Join(vbCrLf, dataDescending))

        Console.ReadLine()

    End Sub

End Module

Outputs:

674,7
373,2
278.5,3
277,5
207.5,1
134,4
58.5,9

Upvotes: 0

Mark
Mark

Reputation: 8160

You can use LINQ to do what you want. If you just want the strings sorted in the correct order you could use:

Dim input = {
    "207.5,1",
    "373,2",
    "278.5,3",
    "134,4",
    "277,5",
    "674,7",
    "58.5,9"
}

Dim sorted =
    From item In input
    Let n = CDec(item.Split(","c)(0))
    Order By n Descending
    Select item

This just converts the first number to decimal to use for sorting. If you want objects with the two numbers extracted, you could use:

Dim sorted2 =
    From item In input
    Let parts = item.Split(","c)
    Select result = New With { .n = CDec(parts(0)), .idx = CInt(parts(1)) }
    Order By result.n Descending

This gives you a IEnumerable of anonymous types with n and idx properties - you could create a class and create instances of that instead if you don't want anonymous types (e.g. you need to return it from a function).

Upvotes: 1

Related Questions