get key associated with max value in a Dictionnary?

To all

Started learning Dictionary to deal with Node Label and an associated value. So far so good…
I am looking for a way of getting the max value in the dictionary and then get the key related to that max value. According to online doc is should be easy but it’s not working as I am getting an error message
Ultimately I’d like to return the top Nth Node ID corresponding to the top Nth value. It seems that this can be done easily with LINQ but as this cannot without an author license I am a bit stuck

So my (simple) thinking is:
1. Find max value in dictionary, returns its key (i.e the Node ID).
2. Add nodeID to list ListNodeIDFinal
3. Remove pair (NodeID,value) from step #1
Loop through step 1-3 N times

Any suggestions?

Thanks
Regards
JXB

Dim mydictionary As New Dictionary(Of Integer, Double)
mydictionary.Add(5001, 10.1)
mydictionary.Add(5002, 20.2)
mydictionary.Add(9999, 99.9)
mydictionary.Add(6001, 30.3)
mydictionary.Add(6002, 40.4)

'BEFORE
For Each pair In mydictionary
theLW.WriteLine(String.Format("{0}, {1}", pair.Key, pair.Value))
Next

Dim maxvalue As Integer = mydictionary.Max(Function(Value) mydictionary.Value)
theLW.WriteLine("" & maxvalue.ToString )

'The Remove method accepts only keys as arguments. To remove the “nth” item from a Dictionary object, use a workaround that uses the Remove and Keys methods:
myDictionary.Remove myDictionary.Keys()(maxvalue)

'AFTER
For Each pair In mydictionary
theLW.WriteLine(String.Format("{0}, {1}", pair.Key, pair.Value))
Next

Dictionaries are great when you want to store related information (key-value pairs) and look it up quickly given a key. Dictionaries are difficult to sort by the values; searching the web will turn up some work-arounds that people have come up with, but dictionaries are just not really built for that purpose.

An alternative would be to use a list object, which has a .Sort method built into them. If you have a list of objects, you can sort them based on any property that you choose. You will have to write a simple function to tell it what value to sort on, but it is pretty straightforward. In the code below, I've created a very simple class to hold the ID and stress values (the key-value pairs in your dictionary); if the objects that you are dealing with already hold this information, you can use the existing objects, you do not have to create your own class for it. The code then sorts the objects based on the stress value. The code sorts them in ascending order, you can change this in the sort function or you can reverse the list after sorting (as in the example code below) to get them in descending order.

Also see:
http://nxjournaling.com/content/sort-list-nx-objects

Option Strict Off

Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF

Module Module44

Dim theSession As Session = Session.GetSession
Dim ufs As UFSession = UFSession.GetUFSession

Sub Main()

Dim lw As ListingWindow = theSession.ListingWindow

Dim workPart As Part = theSession.Parts.Work

lw.Open()

Dim theNodes As New List(Of ExampleClass)
theNodes.Add(New ExampleClass("5001", 10.1))
theNodes.Add(New ExampleClass("5002", 20.2))
theNodes.Add(New ExampleClass("9999", 99.9))
theNodes.Add(New ExampleClass("6001", 30.3))
theNodes.Add(New ExampleClass("6002", 40.4))

lw.WriteLine("'theNodes' original order:")
For Each temp As ExampleClass In theNodes
lw.WriteLine("ID: " & temp.ID & ", stress value: " & temp.Stress.ToString)
Next
lw.WriteLine("")

'sort by stress value (ascending)
theNodes.Sort(AddressOf CompareExampleClassByStress)

lw.WriteLine("'theNodes' sorted by stress value (ascending):")
For Each temp As ExampleClass In theNodes
lw.WriteLine("ID: " & temp.ID & ", stress value: " & temp.Stress.ToString)
Next
lw.WriteLine("")

'reverse the sort order
theNodes.Reverse()

lw.WriteLine("'theNodes' sorted by stress value (descending):")
For Each temp As ExampleClass In theNodes
lw.WriteLine("ID: " & temp.ID & ", stress value: " & temp.Stress.ToString)
Next
lw.WriteLine("")

lw.WriteLine("the top three stress values are:")
lw.WriteLine(theNodes.Item(0).Stress.ToString & ", node ID: " & theNodes.Item(0).ID)
lw.WriteLine(theNodes.Item(1).Stress.ToString & ", node ID: " & theNodes.Item(1).ID)
lw.WriteLine(theNodes.Item(2).Stress.ToString & ", node ID: " & theNodes.Item(2).ID)

End Sub

Function CompareExampleClassByStress(ByVal x As ExampleClass, ByVal y As ExampleClass) As Integer

If x.Stress > y.Stress Then
Return 1
End If

If x.Stress < y.Stress Then
Return -1
End If

If x.Stress = y.Stress Then
Return 0
End If
End Function

End Module

Public Class ExampleClass

Private _id As String
Public Property ID() As String
Get
Return _id
End Get
Set(ByVal value As String)
_id = value
End Set
End Property

Private _stress As Double
Public Property Stress() As Double
Get
Return _stress
End Get
Set(ByVal value As Double)
_stress = value
End Set
End Property

Public Sub New(ByVal id As String, ByVal stressValue As Double)
_id = id
_stress = stressValue
End Sub

End Class

Thanks a lot for the suggestion and example code. I was so "chuffed" with myself learning the Dictionary option!
I did indeed find a function (on the web) to sort a dictionary and it seems to work. You are stretching (a lot) my vb knowledge here!!

Will take the time to go through the example code. Already started to think about "lifting" some of the line I wrote for the dictionary option to deal with a new stress value for a node ID in the List. A node ID can appears only once in the List() but a new stress value can be computed and it must be compared to the one in the List and replaces if found to be greater. See below for "top level"

Thanks
Regards
JXB

'a new Node ID coming from a loop with a new stress value

iMaxNodeLabel = "xxx"
dVMStressMaxvalue = 99999

If theNodes.Contains(iMaxNodeLabel) Then
'get rank of the item where iMaxNodeLabel is. A node ID can only appears once in the List
Dim rank As Integer = theNodes.FindIndex(iMaxNodeLabel)
If theNodes.Item(rank).Stress < dVMStressMaxvalue Then theNodes.Item(rank).Stress= dVMStressMaxvalue
Else
theNodes.Add(New ExampleClass(iMaxNodeLabel, VMStressMaxvalue)
End If

Thanks
Regards

Spent a few minutes looking at the code and while it makes some sense I am struggling a bit with the Class
For example the .Contains "does not work" as it seems that a test which works is:

Dim iMaxNodeLabel As String = "15002"
Dim dVMStressMaxvalue As Double = 999
If theNodes.Contains(New ExampleClass(iMaxNodeLabel, dVMStressMaxvalue)) Then ...

which is not really what I am after as I am trying to check on the Node ID only
I had a look a the msdn website, see link below, but the use of "some many" Private functions is a bit confusing as nothing appears to be straightforward. https://msdn.microsoft.com/en-us/library/x0b5b5bc(v=vs.100).aspx

'example from msdn website
Private Function FindID(ByVal bk As Book) As Boolean
If bk.ID = IDToFind Then
Return True
Else
Return False
End If
End Function

Thanks
Regards

Here is a quick code example that builds on the previous code:

Option Strict Off

Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF

Module Module44

Dim theSession As Session = Session.GetSession
Dim ufs As UFSession = UFSession.GetUFSession

Sub Main()

Dim lw As ListingWindow = theSession.ListingWindow

Dim workPart As Part = theSession.Parts.Work

lw.Open()

Dim theNodes As New List(Of ExampleClass)
theNodes.Add(New ExampleClass("5001", 10.1))
theNodes.Add(New ExampleClass("5002", 20.2))
theNodes.Add(New ExampleClass("9999", 99.9))
theNodes.Add(New ExampleClass("6001", 30.3))
theNodes.Add(New ExampleClass("6002", 40.4))

lw.WriteLine("'theNodes' original order:")
For Each temp As ExampleClass In theNodes
lw.WriteLine("ID: " & temp.ID & ", stress value: " & temp.Stress.ToString)
Next
lw.WriteLine("")

Dim someNode As ExampleClass
'look for ID = 9999
'simulate using some existing variable with value of the ID to find
Dim theID As String = "9999"
Dim newStressValue As Double = 1234
If theNodes.Exists(Function(x) x.ID = theID) Then
lw.WriteLine("node with ID = " & theID & " found, updating stress value to '" & newStressValue.ToString & "'")
someNode = theNodes.Find(Function(x) x.ID = theID)
someNode.Stress = newStressValue
Else
lw.WriteLine("node with ID = " & theID & " NOT found")
End If

'report ID and stress values
lw.WriteLine("current values:")
For Each temp As ExampleClass In theNodes
lw.WriteLine("ID: " & temp.ID & ", stress value: " & temp.Stress.ToString)
Next
lw.WriteLine("")

End Sub

Function CompareExampleClassByStress(ByVal x As ExampleClass, ByVal y As ExampleClass) As Integer

If x.Stress > y.Stress Then
Return 1
End If

If x.Stress < y.Stress Then
Return -1
End If

If x.Stress = y.Stress Then
Return 0
End If
End Function

End Module

Public Class ExampleClass

Private _id As String
Public Property ID() As String
Get
Return _id
End Get
Set(ByVal value As String)
_id = value
End Set
End Property

Private _stress As Double
Public Property Stress() As Double
Get
Return _stress
End Get
Set(ByVal value As Double)
_stress = value
End Set
End Property

Public Sub New(ByVal id As String, ByVal stressValue As Double)
_id = id
_stress = stressValue
End Sub

End Class

The only real new stuff concerns the .Find and .Exists methods of the list object.

If theNodes.Exists(Function(x) x.ID = theID) Then

The code above checks for the existence of an element in the list with a certain ID value. If we have a list of simple value types (such as string or integer), then looking for a certain value is easy. However, if we have a list of custom objects, we need to know what property value(s) we are interested in finding. Similar to when we sorted the list, we now need to write a custom function to test the list member to see if its property value(s) match what we are looking for. Since our function is very simple, we can write it entirely within the 'argument input' of the .Exists method. It follows this format: .Exists(FunctionDeclaration FunctionBody). First we declare that we are using a function (as opposed to a subroutine); we don't need to give it a proper name, since we won't be calling it from anywhere else. So, "Function(x)" says that we are using a function and the parameter will be named "x". The function used for the .Exists method must return a True or False value; the equality test, x.ID = theID, does just that. The .Exists method is responsible for passing in each member of the list to our function. If the function returns True for any member of the list, the .Exists function knows there is at least one member that matches, so it returns True and stops looking.

Writing the function inline like this comes in handy when the function is very simple. We could have created a 'proper' function and used it instead; something like:

.
.
If theNodes.Exists(AddressOf FindID) Then
lw.WriteLine("node exists, using FindID function")
Else
lw.WriteLine("node does NOT exist, using FindID function")
End If
.
.
Function FindID(ByVal theItem As ExampleClass) As Boolean

If theItem.ID = "9999" Then
Return True
Else
Return False
End If

End Function

Perhaps this makes it a bit more clear as to what is happening; however, the inline function is a bit more flexible.

Thanks a lot for the supplied example and explanation. Managed to tweak it for my purpose. Nothing major.
I have done some testing and it seems to work. I have now moved into trying to put all the pieces together.

Thanks a lot again.
Regards
JXB

Thanks
Regards