I have a list of object grouped by this:
Dim duplicates = test.GroupBy(Function(i) i.Total) _
.Where(Function(x) x.Count() > 1) _
.[Select](Function(x) x).ToList
Now I can see throug the debugger that It group correctly the duplicates objects of the list test by a key that is the total property.
Now for each key value I need to get the objects, and than do some stuf with them (for each cycle).
My problem is that I can't access theme in anyway. If I try to cast I always get :Value of type List(Of IGrouping(Of Integer, myObject)) cannot be converted to List(Of myObject))
How can I access this data and manipulate it?
The prospective was wrong. Switched to list of object and changed approach for sorting groupin without linq.
Related
Are there any problems with iterating over a dictionary in the following manner?
Dim dict As New Dictionary(Of String, Integer) From {{"One", 1}, {"Two", 2}, {"Three", 3}}
For i = 0 To dict.Count - 1
Dim Key = dict.Keys(i)
Dim Value = dict.Item(Key)
'Do more work
dict.Item(Key) = NewValue
Next
I have used it a lot without any problems. But I recently read that the best way to iterate over a dictionary was using a ForEach loop. This led me to question the method that I've used.
Update: Note I am not asking how to iterate over a dictionary, but rather if the method that I've used successfully in the past is wrong and if so why.
Are there any problems with iterating over a dictionary in the following manner?
Yes and no. Technically there's nothing inherently wrong with the way you're doing it as it does what you need it to do, BUT it requires unnecessary computations and is therefore slower than simply using a For Each loop and iterating the key/value-pairs.
Iterating keys, then fetching value
The Keys property is not a separate collection of keys, but is actually just a thin wrapper around the dictionary itself which contains an enumerator for enumerating the keys only. For this reason it also doesn't have an indexer that lets you access the key at a specific index like you are right now.
What's actually happening is that VB.NET is utilizing the extension method ElementAtOrDefault(), which works by stepping through the enumeration until the wanted index has been reached. This means that for every iteration of your main loop, ElementAtOrDefault() also performs a similar step-through iteration until it gets to the index you've specified. You now have two loops, resulting in an O(N * N) = O(N2) operation.
What's more, when you access the value via Item(Key) it has to calculate the hash of the key and determine the respective value to fetch. While this operation is close to O(1), it's still an unnecessary additional operation compared to what I'm talking about below.
Iterating key/value-pairs
The dictionary already has an internal list (array) holding the keys and their respective values, so when iterating the dictionary using a For Each loop all it does is fetch each pair and put them into a KeyValuePair. Since it is fetching directly by index this time (at a specific memory location) you only have one loop, thus the fetch operation is O(1), making your entire loop O(N * 1) = O(N).
Based on this we see that iterating the key/value-pairs is actually faster.
This kind of loop would look like (where kvp is a KeyValuePair(Of String, Integer)):
For Each kvp In dict
Dim Key = kvp.Key
Dim Value = kvp.Value
Next
See here:
https://www.dotnetperls.com/dictionary-vbnet
Keys. You can get a List of the Dictionary keys. Dictionary has a get accessor property with the identifier Keys. You can pass the Keys to a List constructor to obtain a List of the keys.
It cites an example similar to yours:
Module Module1
Sub Main()
' Put four keys and values in the Dictionary.
Dim dictionary As New Dictionary(Of String, Integer)
dictionary.Add("please", 12)
dictionary.Add("help", 11)
dictionary.Add("poor", 10)
dictionary.Add("people", -11)
' Put keys into List Of String.
Dim list As New List(Of String)(dictionary.Keys)
' Loop over each string.
Dim str As String
For Each str In list
' Print string and also Item(string), which is the value.
Console.WriteLine("{0}, {1}", str, dictionary.Item(str))
Next
End Sub
End Module
I am currently working on an application that was coded in VB. I am making modifications and adding features to it.
The issue I have is that I want to run a verification check for the ComboBox based on if the value exists before I attempt to select it.
The combo box is populated from a sql query with a dictionary data source
Dim TimerComboSource As New Dictionary(Of String, String)()
TimerComboSource.Add(varSQLReader("ID").ToString, varSQLReader("Name").ToString)
'Binds the values to the comboboxes
cmbTimer.DataSource = New BindingSource(TimerComboSource, Nothing)
cmbTimer.DisplayMember = "Value"
cmbTimer.ValueMember = "Key"
I select a value from a different ComboBox which is populated with a different SQL Query/SQL Table.
When I select the second ComboBox value, the table it comes from contains the ID of the first ComboBox. I want to verify if the Value Exists in the first ComboBox before I select it.
The following does not work:
If cmbTechnician.Items.Contains(varSQLReader("Tech_ID").ToString) Then
cmbTechnician.SelectedValue = varSQLReader("Tech_ID").ToString
End If
Is there a specific way in VB to make this work without it being overly complicated? The other work around would to make a more complicated SQL query but I rather not do that if there's a simpler way.
Since you are using a BindingSource on a Dictionary, you should use the DataSource to determine of things exist. If you tried to add to cmbTimer.Items or delete from it directly, you'd get an error telling you to use the DataSource. So do the same for checking if something exists (dont use a dictionary with local scope):
' form or class level collection
Private cboSource As New Dictionary(Of String, String)
cboSource.Add("red", "red")
cboSource.Add("blue", "blue")
cboSource.Add("green", "green")
cbo.DataSource = New BindingSource(cboSource, Nothing)
cbo.DisplayMember = "Value"
cbo.ValueMember = "Key"
If cbo.Items.Contains("red") Then
Console.Beep() ' wont hit
End If
If cboSource.ContainsValue("red") Then
Console.Beep() ' hits!
End If
The suggestion in comments suggests casting the DataSource of the BindingSource back to dictionary:
Dim tempBS As BindingSource = CType(cbo.DataSource, BindingSource)
Dim newCol As Dictionary(Of String, String) = CType(tempBS.DataSource, Dictionary(Of String, String))
If newCol.ContainsValue("red") Then
Console.Beep() ' hits!
End If
It is easier and more direct to retain a reference to the dictionary, but recreating it will work.
Here are another way =>
If cmbTechnician.FindString(varSQLReader("Tech_ID").ToString) <= -1 Then
'Do Not Exist
End If
This will find the display member and will return an index of the row if there exist value, otherwise will return -1.
More Info are here=>ComboBox.FindString
I'm working on a code that returns a query result like MySqlCommand, all working well but what I'm trying to do is insert the result inside a ComboBox. The way for achieve this is the following:
Form load event execute the GetAvailableCategories function
The function executed download all the values and insert it into a dictionary
Now the dictionary returned need an iteration for each Items to insert in the ComboBox
Practice example:
1,3. Event that fire the function
Private Sub Service_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For Each categoria In Categories.GetAvailableCategories()
service_category.Items.Add(categoria)
Next
End Sub
GetAvailableCategories function
Dim dic As New Dictionary(Of Integer, String)
For Each row In table.Rows
dic.Add(row(0), row(1))
Next
Return dic
How you can see in the 1,3 points I call the function that return the result. What I want to do is insert the row(0) as value of the item and row(1) as Item name. But Actually I get this result in the ComboBox:
[1, Hair cut]
and also I can't access to a specific position of the current item in the iteration. Maybe the dictionary isn't a good choice for this operation?
Sorry if the question could be stupid, but it's a long time since I don't program in vb.net and now I need to brush up a bit.
UPDATE
I've understood that I can assign the value access to the .key of my dictionary, so the result that I want achieve is correct if I do:
cateogoria.key (return the id of record taken from the db)
categoria.value (is the item name that'll display in the ComboBox)
now the problem's that: How to assign the value of the current item without create any other new class? For example:
service_category.Items.Add(categoria.key, categoria.value)
But I can't do this, any idea?
A List as a DataSource sounds like what you are really after. Relying on relative indices in different arrays is sort of flaky. There is not a lot about what these are, but a class would keep the related info together:
Public Class Service
Public Property Name As String
Public Property Category As String
Public Property Id As Int32
End Class
This will keep the different bits of information together. Use them to store the info read from the db and use a List to store all of them:\
Private Services As New List(of Service)
...
For Each row In table.Rows
Dim s As New Service
s.Name = row(0).ToString() '???
s.Category =...
s.Id = ...
Services.Add(s) ' add this item to list
Next
Finally, bind the List to the CBO:
myCbo.DataSource = Services
myCbo.DisplayMember = "Name" ' what to show in cbo
myCbo.ValueMember = "Id" ' what to use for SelectedValue
I dont really know what you want to show or what the db fields read are, so I am guessing. But the larger point is that a Class will keep the different bits of info together better than an array. The List can be the DataSource so that you dont even have to populate the CBO directly. The List can also be Sorted, searched, Filtered and so forth with linq.
When the user picks something, myCbo.SelectedItem should be that item (though it will need to be cast), or you can use SelectedIndex to find it in the list:
thisOne = Services(myCbo.SelectedIndex)
It is also usually a good idea to override ToString in the item/service class. This will determine what shows when a DisplayMember mapping is not available. Without this, WindowsApp2.Service might show for your items:
Public Overrides ToString() As String
Return String.Format("{0} ({1})", Name, Price)
End Sub
This would show something like
Haircut ($12.30)
I am using the below variable to store a list of user ID strings. I then use the list to search for each user using an LDAP query.
Dim userIds As IEnumerable(Of String) =
{"testid1", "testid2", "testid3", "testid4", "testid5"}
That works, but the ID's are hard-coded. How do I make it read the ID's from a ListBox control instead? Would it be something like:
Dim userIds As IEnumerable(Of String) = ListBox1???
I would like to use the ListBox because I will plan to load the ListBox with a bunch of ID's from a text file.
Better yet, is it possible to use a TextBox? If it was a TextBox, I could just copy and paste the ID's that I need to query.
The contents of a ListBox control can be accessed using the ListBox.Items property. It returns a ListBox.ObjectCollection object, which implements IList, ICollection, and IEnumerable.
This is assuming you've added the contents programmatically, rather than binding to a DataSource. If you bound to a DataSource, as LarsTech suggests, you should use ListBox.DataSource.
If you wanted to use a TextBox control, you'd have to manually delimit each ID somehow. You could do this by putting only one ID per line, and then use the Split method to get each ID:
Dim ids as String() = myTextBox.Text.Split(new String() { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
I was going to write this as a comment, but it got a bit long and started involving code examples so I figured it would be better to post it as an answer, even though there is already an accepted answer which is very good.
Since I'm the one who originally gave you the code that used IEnumerable I feel like I should explain why I used it... IEnumerable is the lowest level interface that is implemented by all lists, collections, dictionaries, arrays, etc. Basically, anything that stores multiple data which can be looped through by a For Each loop, implements the IEnumerable interface. In fact, essentially the only thing that the IEnumerable interface supports is the ability to enumerate through its contents with a For Each loop. IEnumerable is just an interface, it's not a concrete object type. Therefore, when you create an IEnumerable variable, that means that variable can be used to reference (i.e. point to) any object that implements that interface (i.e. any object that can be enumerated with a For Each loop.
Therefore, in the following line, it's not creating an IEnumerable type of object. Or at least not in the concrete type sense. It's creating a specific type of object which happens to implement the IEnumerable interface and then sets the ids variable to point to it.
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
The phrase {"1", "2", "3"} is actually a literal expression to represent an array of strings. In other words, that literal expression is the equivalent of doing the following:
Dim stringArray(2) As String
stringArray(0) = "1"
stringArray(1) = "2"
stringArray(2) = "3"
So, since the object containing the list of ID's is actually an array of strings, it could have been done like this:
Dim userIds() As String = {"1", "2", "3"}
However, since I wanted the code to work, regardless of the data source, I used the more general IEnumerable interface. Since the only thing that I actually required of the data was that it could be enumerated with a For Each loop, I didn't want to limit the flexibility of the code by requiring the input list of ID's to be of some higher-level specific object type. I didn't really care that the ID's were specifically an array, or a list, or a dictionary, or a collection. As long as it was something that I could loop through, that's all I cared about. By doing so, that made the code more flexible so that you could set the variable to any enumerable data source, such as the Items property of the ListBox. For instance, all of the following would have worked, without changing the rest of the code:
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
Or
Dim userIdsArray() As String = {"1", "2", "3"}
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIdsArray(2) As String
userIdsArray(0) = "1"
userIdsArray(1) = "2"
userIdsArray(2) = "3"
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIds As IEnumerable(Of String) = ListBox1.Items.OfType(Of String)()
Or
Dim userIds As IEnumerable(Of String) = File.ReadAllLines("IDs.txt")
Or
Dim userIds As IEnumerable(Of String) = TextBox1.Text.Split({Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
Etc.
Since all of those above data sources implement the IEnumerable interface, the same userIds variable can be used to reference all of them.
I'm surprised at how hard this has been to do but I imagine it's a quick fix so I will ask here (searched google and documentation but neither helped). I have some code that adds items to a collection using keys. When I come across a key that already exists in the collection, I simply want to set it by adding a number to the current value.
Here is the code:
If CollectionItemExists(aKey, aColl) Then 'If key already has a value
'add value to existing item
aColl(aKey).Item = aColl(aKey) + someValue
Else
'add a new item to the collection (aka a new key/value pair)
mwTable_ISO_DA.Add someValue, aKey
End If
The first time I add the key/value pair into the collection, I am adding an integer as the value. When I come across the key again, I try to add another integer to the value, but this doesn't work. I don't think the problem lies in any kind of object mis-match or something similar. The error message I currently get is
Runtime Error 424: Object Required
You can't edit values once they've been added to a collection. So this is not possible:
aColl.Item(aKey) = aColl.Item(aKey) + someValue
Instead, you can take the object out of the collection, edit its value, and add it back.
temp = aColl.Item(aKey)
aColl.Remove aKey
aColl.Add temp + someValue, aKey
This is a bit tedious, but place these three lines in a Sub and you're all set.
Collections are more friendly when they are used as containers for objects (as opposed to containers for "primitive" variables like integer, double, etc.). You can't change the object reference contained in the collection, but you can manipulate the object attached to that reference.
On a side note, I think you've misunderstood the syntax related to Item. You can't say: aColl(aKey).Item. The right syntax is aColl.Item(aKey), or, for short, aColl(aKey) since Item is the default method of the Collection object. However, I prefer to use the full, explicit form...
Dictionaries are more versatile and more time efficient than Collections. If you went this route you could run an simple Exists test on the Dictionary directly below, and then update the key value
Patrick Matthews has written an excellent article on dictionaries v collections
Sub Test()
Dim MyDict
Set MyDict = CreateObject("scripting.dictionary")
MyDict.Add "apples", 10
If MyDict.exists("apples") Then MyDict.Item("apples") = MyDict.Item("apples") + 20
MsgBox MyDict.Item("apples")
End Sub
I think you need to remove the existing key-value pair and then add the key to the collection again but with the new value