Currently I'm trying to add values into a nested dictionary using VB. I have it working for a flat dictionary, but can't quite get my head around the syntax for doing it nested.
What I have so far is, I have commented the lines I'm having trouble with:
Public Shared Dim dictionary AS New System.Collections.Generic.Dictionary(Of String, System.Collections.Generic.Dictionary(Of String, Integer))
Function addValue(ByVal code AS String, ByVal cust AS String,ByVal value AS Integer)
Dim innerDict AS New System.Collections.Generic.Dictionary(Of String, Integer)
innerDict.Add(cust,value);
IF dictionary.ContainsKey(code) Then
IF dictionary.Item(code).ContainsKey(cust) Then 'Can I access the Customer key in this way?
dictionary.Item(code).Item 'Here I need to update the value held by customer to the old value + new value.
Else
dictionary(code).Add(cust,value) 'Is this syntax correct?
End If
Else
dictionary.Add(code,innerDict)
End If
End Function
What I want to happen is to have a dictionary structured as follow:
Code1:
Customer1: 12
Customer2: 13
Code 2:
Customer1: 12
Customer2: 13
Here is the function that will do what you want.
The first thing it does, is checks whether an entry exists for code in dictionary. If none exists, it adds one whose value is an empty dictionary that will receive the cust-value pairs.
Currently the function does not return any value. If no value is to be returned, you should use a Sub.
Function addValue(ByVal code As String, ByVal cust As String, ByVal value As Integer)
' If no entry for code, create one.
If Not dictionary.ContainsKey(code) Then
dictionary.Add(code, New System.Collections.Generic.Dictionary(Of String, Integer))
End If
' Add cust, value to entry at code.
dictionary(code).Add(cust, value)
End Function
' Returns sum the customer's values.
Function SumCustomerValues(customer As String) As Integer
Dim sum As Integer = 0
For Each d As KeyValuePair(Of String, System.Collections.Generic.Dictionary(Of String, Integer)) In dictionary
If d.Value.ContainsKey(customer) Then
sum += d.Value(customer)
End If
Next
Return sum
End Function
Related
I'm trying to calculate the total count and value of orders for a customer, using the code below.
Dim iOrderCount As Integer
Dim iLineCount As Integer
Dim cTotalGoods As Decimal
Dim cTotalVat As Decimal
Dim cTotalDelivery As Decimal
Using manOrders As New CManOrders(m_dbSql)
manOrders.GetOrdersInProgress(sAccountCode, iOrderCount, iLineCount, cTotalGoods, cTotalVat, cTotalDelivery)
When I assign values to these variables in the GetOrdersInProgress subroutine, the values are being assigned correctly, which I can see when I step through the code.
Public Sub GetOrdersInProgress(sAccountCode As String, ByRef RET_orderCount As Integer, ByRef RET_lineCount As Integer,
ByRef RET_totalGoods As Decimal, ByRef RET_totalVat As Decimal, RET_totalDelivery As Decimal)
...
For Each dr As DataRow In m_dbSql.getDataTable(sql).Rows
RET_orderCount = dbToInt(dr(ORDER_COUNT))
RET_lineCount = dbToInt(dr(LINE_COUNT))
RET_totalGoods = dbToDecimal(dr(TOTAL_GOODS))
RET_totalVat = dbToDecimal(dr(TOTAL_VAT))
RET_totalDelivery = dbToDecimal(dr(2))
Return
Next
However, once I step through and move back into where the GetOrdersInProgress subroutine is called from, all of the values in the variables are returned correctly, except for RET_totalDelivery - the new one I've added to another developer's project.
The value in the RET_totalDelivery variable in the Public Sub GetOrdersInProgress... line is correct and it's correct after the assignment, but when it reaches Return and the variables are then used in the parent subroutine, for some reason they're all correct except for the new one I've added, RET_totalDelivery. I'd understand if the value wasn't being assigned correctly, however it is.
Why would it be returning 0 all the time?
By default, arguments passed to vb.net methods are passed by value, or ByVal. You did not specify ByRef in your RET_totalDelivery argument in GetOrdersInProgress.
Changes made to arguments passed by value are not retained when the method ends.
Your Sub should now be...
Public Sub GetOrdersInProgress(sAccountCode As String, ByRef RET_orderCount As Integer, ByRef RET_lineCount As Integer, ByRef RET_totalGoods As Decimal, ByRef RET_totalVat As Decimal, ByRef RET_totalDelivery As Decimal)
I prefer to write the method as a function. Write a class to hold all the return values. Then change the method into a function with just input parameters and return the values you fetch from sql.
Sub Main
Dim bl = New OrdersInProgressBusinessLogic()
Dim ordersInProgress = bl.GetOrdersInProgress("some account code")
End Sub
Public Class OrdersInProgress
Public Property OrderCount As Integer
Public Property LineCount As Integer
Public Property TotalGoods As Decimal
Public Property TotalVat As Decimal
Public Property TotalDelivery As Decimal
End Class
Public Class OrdersInProgressBusinessLogic
Public Function GetOrdersInProgress(sAccountCode As String) As OrdersInProgress
Dim ordersInProgress = New OrdersInProgress()
' some code here to fetch data from sql
For Each dr As DataRow In m_dbSql.getDataTable(sql).Rows
With ordersInProgress
.OrderCount = dbToInt(dr(ORDER_COUNT))
.LineCount = dbToInt(dr(LINE_COUNT))
.TotalGoods = dbToDecimal(dr(TOTAL_GOODS))
.TotalVat = dbToDecimal(dr(TOTAL_VAT))
.TotalDelivery = dbToDecimal(dr(2))
End With
Next
Return ordersInProgress
End Function
' some other functions/subs for OrdersInProgress class
End Class
I need to create a 2D dictionary/keyvalue pair.
I tried something like this.
Dim TwoDimData As New Dictionary(Of String, Dictionary(Of String, String))
'Create an empty table
For Each aid In AIDList '(contains 15000 elements)
TwoDimData.Add(aid, New Dictionary(Of String, String))
For Each bid In BIDList 'contains 30 elements
TwoDimData.Item(aid).Add(bid, "")
Next
Next
'Later populate values.
[some code here to populate the table]
'Now access the value
'The idea is to access the info as given below (access by row name & col name)
Msgbox TwoDimData.Item("A004").Item("B005") ' should give the value of 2
Msgbox TwoDimData.Item("A008").Item("B002") ' should return empty string. No error
Issue:
The issue is in Creating the empty table. It takes 70 seconds to create the TwoDimData table with empty values. Everything else seems to be fine. Is there any way to improve the performance - may be instead of using Dictionary?
I suggest you try Dictionary(Of Tuple(Of String, String), String) instead. That is, the keys are pairs of strings (Tuple(Of String, String)) and the values are strings. That would appear to correspond nicely to the diagram in your question.
Dim matrix As New Dictionary(Of Tuple(Of String, String), String)
' Add a value to the matrix:
matrix.Add(Tuple.Create("A003", "B004"), "3")
' Retrieve a value from the matrix:
Dim valueAtA003B004 = matrix(Tuple.Create("A003", "B004"))
Of course you can define your own key type (representing a combination of two strings) if Tuple(Of String, String) seems too generic for your taste.
Alternatively, you could also just use (possibly jagged) 2D arrays, but that would potentially waste a lot of space if your data is sparse (i.e. if there are many empty cells in that 2D matrix); and you'd be forced to use numeric indices instead of strings.
P.S.: Actually, consider changing the dictionary value type from String to Integer; your example matrix suggests that it contains only integer numbers, so it might not make sense to store them as strings.
P.P.S.: Do not add values for the "empty" cells to the dictionary. That would be very wasteful. Instead, instead of simply retrieving a value from the dictionary, you check whether the dictionary contains the key:
Dim valueA As String = "" ' the default value
If matrix.TryGetValue(Tuple.Create("A007", "B002"), valueA) Then
' the given key was present, and the associated value has been retrieved
…
End If
I would think that a simple structure would suffice for this?
Public Structure My2DItem
Public Row As Integer
Public Col As Integer
Public Value As String
End Structure
Public My2DArray As Generic.List(Of My2DItem) = Nothing
Public Size As Integer
Public MaxRows As Integer
Public MaxCols As Integer
'
Sub Initialise2DArray()
'
Dim CountX As Integer
Dim CountY As Integer
Dim Item As My2DItem
'
'initialise
MaxRows = 15000
MaxCols = 30
Size = MaxRows * MaxCols
My2DArray = New Generic.List(Of My2DItem)
'
'Create an empty table
For CountY = 1 To 15000
For CountX = 1 To 30
Item = New My2DItem
Item.Row = CountY
Item.Col = CountX
Item.Value = "0"
My2DArray.Add(Item)
Item = Nothing
Next
Next
'
End Sub
And to read the data out of the array,
Function GetValue(Y As Integer, X As Integer) As String
'
Dim counter As Integer
'
GetValue = "Error!"
If My2DArray.Count > 0 Then
For counter = 0 To My2DArray.Count - 1
If My2DArray(counter).Row = Y Then
If My2DArray(counter).Col = X Then
GetValue = My2DArray(counter).Value
Exit Function
End If
End If
Next
End If
'
End Function
And to read your sample cell A004 B005
MyStringValue = GetValue(4,5)
I would suggest creating a class that has properties for the AID and BID and use this as the basis for the values you want to store
Public Class AIdBId
Public Property AId As Integer
Public Property BId As Integer
Public Sub New(aId As Integer, bId As Integer)
Me.AId = aid
Me.BId = bid
End Sub
End Class
Note that I have used integers for everything because it seems that is all you need and it is more efficient that using a string
Then you can add values where they are non-zero:
'define your dictionary
Dim valueMatrix As New Dictionary(Of AIdBId, Integer)
'add your values
valueMatrix.Add(New AIdBId(1, 1), 1)
valueMatrix.Add(New AIdBId(2, 3), 1)
valueMatrix.Add(New AIdBId(4, 3), 3)
valueMatrix.Add(New AIdBId(5, 8), 8)
'check if a value exixts
Dim valueExixsts As Boolean = valueMatrix.ContainsKey(New AIdBId(9, 9))
'get a value
Dim i As Integer = valueMatrix(New AIdBId(4, 3))
So you can now combine these two to return the value if there is one or zero if not:
Private Function GetValue(valuematrix As Dictionary(Of AIdBId, Integer), aId As Integer, bId As Integer) As Integer
Dim xRef As New AIdBId(aId, bId)
If valuematrix.ContainsKey(xRef) Then
Return valuematrix(xRef)
Else
Return 0
End If
End Function
I have a SortedDictionary:
Dim myFilterItems As SortedDictionary(Of String, FilterItem)
myFilterItems = New SortedDictionary(Of String, FilterItem)(StringComparer.CurrentCultureIgnoreCase)
The FilterItem class is defined like this:
Private Class FilterItem
Public ValueToSort As Object
Public IsChecked As Boolean
Public IsAbsent As Boolean = False
End Class
I need to enumerate my SortedDictionary sorted by the FilterItem.ValueToSort property. With LINQ, it's easy to do - we get the corresponding IEnumerable and then use For Each:
Dim mySortedValueList As IEnumerable(Of KeyValuePair(Of String, FilterItem))
mySortedValueList = From entry In myFilterItems Order By entry.Value.ValueToSort Ascending
For Each entry As KeyValuePair(Of String, FilterItem) In mySortedValueList
' ...
Next
How to do that effectively in .NET 2.0?
I rewrote my code using Lists as Jon Skeet suggested. As I can see from my tests, I have the same performance like with the LINQ query - or even 5-10% gain. The new version of code looks like this:
Dim mySortedValueList As New List(Of KeyValuePair(Of String, FilterItem))(myFilterItems)
If iArr <> iGAFMValueType.Text Then
mySortedValueList.Sort(AddressOf CompareFilterItemsByValuesToSort)
End If
Private Shared Function CompareFilterItemsByValuesToSort(ByVal itemX As KeyValuePair(Of String, FilterItem), ByVal itemY As KeyValuePair(Of String, FilterItem)) As Integer
Dim myValueX As IComparable = TryCast(itemX.Value.ValueToSort, IComparable)
Dim myValueY As IComparable = TryCast(itemY.Value.ValueToSort, IComparable)
If (myValueX Is Nothing) Then
If (myValueY Is Nothing) Then
Return 0
End If
Return -1
End If
If (myValueY Is Nothing) Then
Return 1
End If
Dim myTypeX As Type = myValueX.GetType()
Dim myTypeY As Type = myValueY.GetType()
If (myTypeX <> myTypeY) Then
Return String.CompareOrdinal(myTypeX.Name, myTypeY.Name)
End If
Return myValueX.CompareTo(myValueY)
End Function
However, while working on this comparison algorithm, I found that I can't compare values of some numeric types - though they can be compared (for instance, SByte and Double values). BTW, the original LINQ query also fails in this case. But this is another story that continues in this question:
How to compare two numeric values (SByte, Double) stored as Objects in .NET 2.0?
I have a problem...
I am trying to put into a list of String dictionary keys values if condition of containsvalue is true:
But, this is not correct :(
here is a code:
Private listID As New List(Of String) ' declaration of list
Private dictionaryID As New Dictionary(Of String, Integer) ' declaration of dictionary
'put a keys and values to dictionary
dictionaryID.Add("first", 1)
dictionaryID.Add("second", 2)
dictionaryID.Add("first1", 1)
If dictionaryID.ContainsValue(1) Then ' if value of dictinary is 1
Dim pair As KeyValuePair(Of String, Integer)
listID.Clear()
For Each pair In dictionaryID
listID.Add(pair.Key)
Next
End If
And now, list must have two elements... -> "first" and "first1"
Can you help me?
Thank you very much!
You are looping through the whole dictionary and add all the elements to the list. You should put an if statement in the For Each or use a LINQ query like this:
If listID IsNot Nothing Then
listID.Clear()
End If
listID = (From kp As KeyValuePair(Of String, Integer) In dictionaryID
Where kp.Value = 1
Select kp.Key).ToList()
Using an if statement:
listID.Clear()
For Each pair As KeyValuePair(Of String, Integer) In dictionaryID
If pair.Value = 1 Then
listID.Add(pair.Key)
End If
Next
My VB.Net is a little rusty, but it looks like you were adding all of them, no matter if their value was 1 or not.
Private listID As New List(Of String) ' declaration of list
Private dictionaryID As New Dictionary(Of String, Integer) ' declaration of dictionary
'put a keys and values to dictionary
dictionaryID.Add("first", 1)
dictionaryID.Add("second", 2)
dictionaryID.Add("first1", 1)
If dictionaryID.ContainsValue(1) Then ' if value of dictinary is 1
Dim pair As KeyValuePair(Of String, Integer)
listID.Clear()
For Each pair In dictionaryID
If pair.Value = 1 Then
listID.Add(pair.Key)
End If
Next
End If
I use the following code to fill the Table1 dictionary with the information found within the LINQ query.
Dim DB As New DatabaseDataContext
Dim Table1 As New Dictionary(Of String, Integer)
Dim Table2 As New Dictionary(Of String, Integer)
Private Function FillTable() As Dictionary(Of String, Integer)
Table1.Clear()
Dim Query = From c In DB.Table1 Select New With _
{.Table1ID = c.Table1ID, .Table1 = c.Table1}
For Each c In Query
Table1.Add(c.Table1, c.Table1ID)
Next
Return Table1
End Function
What changes should i make to the function above to fill any given TableXXX dictionary?
You see, I would not like to use the function below to fill the Table2 dictionary.
Private Function FillTable2() As Dictionary(Of String, Integer)
Table2.Clear()
Dim Query = From c In DB.Table2 Select New With _
{.Table2ID = c.Table2ID, .Table2 = c.Table2}
For Each c In Query
Table2.Add(c.Table2, c.Table2ID)
Next
Return Table2
End Function
What about the "ToDictionary()" extension method?
http://msdn.microsoft.com/en-us/library/bb549277.aspx
I don't know if this is true but this seems the VB version of the MS c# SimpleLinqToDatabase sample app.
If that is so it would work if you make all your Table row data model types have the same properties TableID and table. Then getting the table data with the generic method. If you don't want that you will need to alter the base model to have a property accessor by string with reflection, but that is not very clever and fast to do on the datamodel.
Private Function FillTable(Of T)() As Dictionary(Of String, Integer)
Dim dict as New Dictionary(Of String, Integer)
Dim Query = From c In DB.GetTable(Of T) Select New With _
{.TableID = c.TableID, .Table = c.Table}
For Each c In Query
dict.Add(c.Table, c.TableID)
Next
Return dict
End Function
Then call this with:
Dim result as Dictionary(Of String, Integer)
result = FillTable(Of Table1)()