How can I get a hashtable key name from a value in VB.NET? - vb.net

I have a hashtable in VB.NET and I need to get the string value of a key from it's value. For example, if I do:
hashtable.add("string1","string2")
How would I get the value "string1" if I had "string2"?

You can't (at least not without simply looping through every value). Consider the fact that multiple keys can map to the same value:
hashtable.Add("string1", "string2")
hashtable.Add("string3", "string2")
Now given "string2" what would you expect to be returned?
If you really need to do a "reverse" lookup, then the simplest solution is to probably have two hashtable, one for the "forward" lookup and one for the "reverse" lookup.

As Dean / codeka says you can't strictly do this.
However you can do something like this as the Keys and Values of a Hashtable are in the same (unspecified) order:
Hashtable ht = new Hashtable();
ht.Add("string1", "str2");
ht.Add("string2", "str2");
List<string> keys = new List<string>(ht.Keys.OfType<string>());
string key = ht.Values.OfType<string>()
.Select((htI, i) => new { Key = keys[i], Value = htI })
.Where(htKVP => htKVP.Value == "str2")
.Select(htKVP => htKVP.Key)
.FirstOrDefault();
However, you would be better using a Dictionary<string, string> just because it is generically typed and lets you get to Linq easier.
Converted for VB.NET that is:
Dim ht as new Hashtable()
ht.Add("string1", "str2")
ht.Add("string2", "str2")
Dim keys as new List(Of String)(ht.Keys.OfType(Of String)())
Dim key as String = ht.Values.OfType(Of String)() _
.Select(Function(htI, i) New With { .Key = keys(i), .Value = htI }) _
.Where(Function(htKVP) htKVP.Value = "str2") _
.Select(Function(htKVP) htKVP.Key) _
.FirstOrDefault()
But again I'd start with:
Dim dt as New Dictionary(Of String, String)
You could add this as an extension method like so:
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension()>
Public Function FirstKeyForValue(ByVal Hashtable as ht, ByVal value As String) As String
return ht.Values.OfType(Of String)() _
.Select(Function(htI, i) New With { .Key = keys(i), .Value = htI }) _
.Where(Function(htKVP) htKVP.Value = "str2") _
.Select(Function(htKVP) htKVP.Key) _
.FirstOrDefault()
End Function
End Module

There is a much easier way than Matt's answer. You can perform linq on a hashtable.
Of course the below sample code can be modified to use the proper types used in your Hashtable variable as both Key and Value can be any type:
Public Class HashtableTest
Private Lookup As New Hashtable
Private Sub New()
Lookup("Key1") = "Value1"
Lookup("Key2") = "Value2"
End Sub
Public Sub Test()
MsgBox(GetKey("Value2"))
End Sub
Public Function GetKey(Value As String) As String
Dim FoundKey As String = ""
If Lookup.ContainsValue(Value) Then
FoundKey = (From Key As String In Lookup.Keys.Cast(Of String) Where Lookup(Key).ToString() = Value Select Key).FirstOrDefault()
End If
Return FoundKey
End Function
End Class

Related

LINQ query is not working

I am trying to create a dictionary from a list. I am trying to filter the list such that it contains the id that I am adding as a key to the dictionary. So dictionary would be Key, List - with id as Key.
I have no idea why the following won't work - although the rawlist contains ids, filteredlist is empty. I am checking the value within the loop. Fresh set of eyes please?
resultList is dictionary(string, List)
For Each c As String In listIds
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Contains(c.Trim())).ToList()
resultList.Add(c,filteredlist)
Next
I need the filtered list. I have tried Contains, Equals and "="
i.e.
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Equals(c.Trim())).ToList()
and
Dim filteredlist = rawList.Where(Function(x) x.id.Trim() = (c.Trim())).ToList()
Please look into it thanks!
Looks like you have it backwards. Typically you have a list for looking for something first and not iterate through that list which IMHO takes longer.
Filter Linq Child Collection by another List/Array
Public Class POC
Public Property Id As Integer
Public Property Desc As String
Public Property Orders As List(Of Order)
End Class
Public Class Order
Public Property Id As Integer
Public Property Desc As String
End Class
Private Shared Function GetOrders(numberOfOrders As Integer) As List(Of Order)
Dim orders = New List(Of Order)()
For i As Integer = 1 To numberOfOrders
orders.Add(New Order() With { .Id = i, .Desc = "{i} Order" })
Next
Return orders
End Function
Private Shared Function GetPOCOsAndOrders() As List(Of POC)
Return New List(Of POC)() From { _
New POC() With { .Id = 1, .Desc = "John", .Orders = GetOrders(1) },
New POC() With { .Id = 2, .Desc = "Jane", .Orders = GetOrders(2) },
New POC() With { .Id = 3, .Desc = "Joey", .Orders = GetOrders(3) }
End Function
Private Shared Sub Main(args As String())
Dim orders = New List(Of Integer)() From { 2, 3 }
Dim items = GetPOCOsAndOrders()
Dim peopleAndOrdersWhereOrderNumberIsGreaterThanTwo = items.Where(Function(x) x.Orders.Any(Function(y) orders.Contains(y.Id)))
'I should only get the last two people out of three and their orders
peopleAndOrdersWhereOrderNumberIsGreaterThanTwo.ToList().ForEach(Function(x) Console.WriteLine("{x.Id} {x.Desc} {x.Orders.Count}"))
Console.ReadLine()
End Sub
There are many silght variations possible with the limited information you have given us, but a working example is:
Option Infer On
Option Strict On
Module Module1
Public Class datum
Property Id As String
Property Name As String
Public Sub New()
' empty constructor
End Sub
Public Sub New(Id As String, Name As String)
Me.Id = Id
Me.Name = Name
End Sub
End Class
Sub Main()
' sample data...
Dim rawData = New List(Of datum)
rawData.Add(New datum("cat", "Lulu"))
rawData.Add(New datum("cat", "Uschi"))
rawData.Add(New datum("snake", "Sid"))
rawData.Add(New datum("fox", "Reynard"))
rawData.Add(New datum("mouse", "Jerry"))
' what to look for:
Dim listIds As New List(Of String) From {"CAT", "mouse", "ELK"}
' somewhere to store the results:
Dim dict As New Dictionary(Of String, List(Of String))
' filter by what to look for:
For Each c In listIds
Dim foundItems = rawData.Where(Function(x) String.Compare(x.Id, c, StringComparison.OrdinalIgnoreCase) = 0)
' only add items to the dictionary if the Id was found:
If foundItems.Count() > 0 Then
' use the case of the id from the raw data:
dict.Add(foundItems(0).Id, foundItems.Select(Function(f) f.Name).ToList())
End If
' alternative which includes dictionary entries where the Id was not found:
'dict.Add(If(foundItems.Count = 0, c, foundItems(0).Id), foundItems.Select(Function(f) f.Name).ToList())
Next
' show the dictionary contents:
For Each kvp In dict
Console.WriteLine(kvp.Key & ": " & String.Join(", ", kvp.Value))
Next
Console.ReadLine()
End Sub
End Module
Output:
cat: Lulu, Uschi
mouse: Jerry
I have included commented-out code for some variations you may want.

How to query an arraylist using Linq that stores dictionaries

The following code reads query results from oracle data reader and stores each record in a dictionary and appends the dictionaries to an array list :
Dim dr As OracleDataReader = cmd.ExecuteReader()
'loop oracle data records and store them to dictionaries
'append dictionaries to an array list
Dim arr As New ArrayList
While dr.Read
Dim dict As New Dictionary(Of String, Object)
For count As Integer = 0 To (dr.FieldCount - 1)
dict.Add(dr.GetName(count), dr(count))
Next
arr.Add(dict)
End While
How do I write a LINQ query that can be used to retrieve values from the dictionaries stored in the array list? Please help. I've been searching and have not got any good answers
First of all, don't use ArrayList, ever. It is there for backwards compatibility but has no usage. I can make answer short - there is no use of LINQ with ArrayList. Use generic List(Of T) and LINQ to search values in it. No need for Dictionary either. This is the old style. We used Dictionary because it has key
I see, you trying to create your table structure but no need for this. First of all, there is System.Data.DataTable, which can be queried on client.
Or use this technique
Public Class User
Public Property Id As Integer
Public Property Name As String
Public Property Email As String
Public Property Country As String
End Class
Private Function LoadUsers() As List(Of User)
Dim uList As New List(Of User)()
' Some Code goes here
While dr.Read()
Dim u As New User()
u.Id = dr("Id")
u.Name = dr("Name")
u.Email = dr("Email")
u.Country = dr("Country")
uList.Add(u)
End While
. . . . . . .
Return uList
End While
' somewhere in class set member variable
_users = LoadUsers()
' And then you can search for info using LINQ
Public Function FindByCountry(ByVal country As String) As List(Of User)
Return _users.Where(Function(u) u.Country.Equals(country, StringComparison.OrdinalIgnoreCase))
End
The downside of this approach - you need Find function for each field. But what if you can pass a function itself. See- you have Name, email, Country - all strings. Here what you can do
Class Client
Sub SearchStrings(ByVal searchOption String, Byval searchValue As String)
Dim f As Func(Of User, boolean)
If searchOption = "Name" Then
f = Function(u as User)(u.Name.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
ElseIf searchOption = "Country" Then
f = Function(u as User)(u.Country.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
ElseIf searchOption = "Email" Then
f = Function(u as User)(u.Email.Equals(searchValue , Stringcomparison.OrdinalIgnoreCase))
Else
. . . .
End If
dataGrd.DataSource = myRepository.FindByString(f)
End Sub
End Class
' In your repository class
public sub FindByString(ByVal f as Func(Of String, Boolean)) As List(Of User)
_users.Where(f).ToList()
End sub
' use this to search single user
public sub FindByInteger(ByVal f as Func(Of Integer, Boolean)) As User
_users.SingleOrDefault(f)
End sub
The bottom line - drop what you do and use modern and efficient techniques. And above are just couple of them

How to cast result back to generic list (using linq to object)?

Dim a As New List(Of EntityTableRow)
a = myTable1.TableRows
Dim lFinal2 = (From el In a Group el By Key = New With {Key el.Description, Key el.Activity, Key el.ServiceGroup, Key el.SubServiceGroup, Key el.ResourceGroup, Key el.Country, Key el.DCN, Key el.Workforce, Key el.RateType, Key el.LoadType} _
Into Group Select New With { _
.PageNum = "", _
.Description = Key.Description, _
.Activity = Key.Activity, _
.MonthCosts = (From k In Group.SelectMany(Function(g) g.MonthCosts.Keys).Distinct() _
Select New With {.Month = k, .Sum = Group.Sum(Function(g) _
If(g.MonthCosts.ContainsKey(k), g.MonthCosts(k), 0))}).ToDictionary(Function(i) i.Month, Function(i) i.Sum)})
I am not able to cast the above result back into the original object form from the below code:
Dim myTable1_grouped As New EntityDATATable(Of EntityTableRow)
myTable1_grouped.TableRows = CType(lFinal2, List(Of EntityTableRow))
original class is like below. The class has a number of more string properties, which I have omitted for this snippet. All those properties also are using in the grouping in above linq code.:
Public Class EntityTableRow
Public PageNum As Integer
Public Description As String
Public Activity As String
.....
.....
.....
Public MonthCosts As New Dictionary(Of Integer, Double)
End Class
The error I am getting is:
System.InvalidCastException was caught
Message="Unable to cast object of type 'WhereSelectEnumerableIterator2[VB$AnonymousType_12[VB$AnonymousType_010[System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String],System.Collections.Generic.IEnumerable1[Addin.EntityTableRow]],VB$AnonymousType_235[System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.Collections.Generic.Dictionary2[System.Int32,System.Double]]]' to type 'System.Collections.Generic.List`1[Addin.EntityTableRow]'."
There are 2 things you have to do there:
Set your class name when creating the results:
Into Group Select New EntityTableRow With { _
instead of:
Into Group Select New With { _
And add ToList() to enumerate and get results into a List<EntityTableRow>:
myTable1_grouped.TableRows = CType(lFinal2.ToList(), List(Of EntityTableRow))

How to write the contents of a dictionary to a MessageBox

In VB.NET I want to write the contents of a dictionary to a message box.
The dictionary is rather basic
Dim x As New Dictionary(Of Integer, Users)
x.Add("1", New Users("1", "Simon"))
The user class contains 2 attributes, user ID (Integer) and Username (String).
I am struggling to write the dictionary contents. I would like to write each dictionary entry to a string but i am having no success as I keep getting the error message:
Argument 'Prompt' cannot be converted to type 'String'.
You are passing a string where you specified an integer:
Fix:
Dim x As New Dictionary(Of Integer, Users)
x.Add(1, New Users(1, "Simon"))
Then to show the contents:
Dim sb As New StringBuilder
For Each item As KeyValuePair(Of Integer, Users) In x
sb.AppendLine(item.Key & ") " & item.Value.ToString)
Next
MessageBox.Show(sb.ToString())
Your Users class would need to override the ToString function or change the ToString call to the property in Users that shows the user's name.
Update to Users class:
Public Class Users
Private _p1 As Integer
Private _p2 As String
Sub New(ByVal p1 As Integer, ByVal p2 As String)
_p1 = p1
_p2 = p2
End Sub
Public Overrides Function ToString() As String
Return _p2
End Function
End Class
Here you go.
Dim sbMessage As New System.Text.StringBuilder(500)
For Each wKey As Integer In x.Keys
sbMessage.Append("Key = ").Append(wKey).Append(", Value = ").Append(x.Item(wKey).ToString()).AppendLine()
Next
MessageBox.Show(sbMessage.ToString)
To make this useful, you will need to override the ToString method in the Users class. For example, assuming that there is an ID and a name in this class:
Public Overrides Function ToString() As String
Dim sbText As New System.Text.StringBuilder(500)
sbText.Append("ID = ").Append(Me.Id).Append(", Name = ").Append(Me.Name)
Return sbText.ToString
End Function
For Each kvp As KeyValuePair(Of Integer, Users) In x
Console.WriteLine("Key = {0}, Value = {1}", _
kvp.Key, kvp.Value)
Next kvp
Something like that, bearing in mind that your Value will be a Users object, and that as #LarsTech said, you should pass in an integer instead of a string into the Dictionary

How to convert a string of key/value pairs to HashTable or Dictionary or?

In VB.NET, how can I convert the following string into some kind of key/value type such as a Hashtable, Dictionary, etc?
"Name=Fred;Birthday=19-June-1906;ID=12345"
I want to extract Birthday or ID without having to split the string into an array.
EDIT: I'd prefer not to split the string into an array in case the format of the string changes later. I don't have control over the string. What if someone switches the order around or adds another element?
I’m currently unable to test this, lacking a VB compiler, but the following solution should also work, and it has the advantage of not requiring an explicit loop. It uses the Linq method ToDictionary and two nested Split operations:
Dim s = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d = s.Split(";"c).Select(Function (kvp) kvp.Split("="c)) _
.ToDictionary( _
Function (kvp) kvp(0), _
Function (kvp) kvp(1))
First, we split on the outer delimiter (i.e. the semi-colon). From the resulting array, we select by splitting again, this time on =. The resulting array of arrays is converted to a dictionary by specifying that the first item is to become the key and the second is to become the value (the identifier kvp stands for “key-value pair”).
Since I can’t check the exact VB syntax and the above may contain subtle errors, here is the equivalent C# code (tested for correctness):
var s = "Name=Fred;Birthday=19-June-1906;ID=12345";
var d = s.Split(';').Select(kvp => kvp.Split('='))
.ToDictionary(kvp => kvp[0], kvp => kvp[1]);
Not sure why you don't want to split it. If you're sure there won't be any extra = or ; then you could just do:
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d As New Dictionary(Of String, String)
For Each temp As String In s.Split(";"c)
Dim index As Int32 = temp.IndexOf("="c)
d.Add(temp.Substring(0, index), temp.Substring(index + 1))
Next
Which might not be beautiful, but is very easy to understand.
input.Split(";"c) returns an array of key/value:
{ "Name=Fred", "Birthday=19-June-1906" , "ID=12345" }
so pair.Split("="c) returns { "Name", "Fred" } etc
If you want an alternative to doing a String.Split; there is always Regular Expressions as an alternative:
Dim map As Dictionary(Of String, String) = New Dictionary(Of String, String)
Dim match As Match = Regex.Match("Name=Fred;Birthday=19-June-1906;ID=12345", "(?<Name>[^=]*)=(?<Value>[^;]*);?")
While (match.Success)
map.Add(match.Groups("Name").Value, match.Groups("Value").Value)
match = match.NextMatch()
End While
The regular expression itself could be beefed up to better handle whitespace between key/value's and pair's but you hopefully get the idea. This should only pass through the string once to build up a string dictionary of keys and values.
Dim persSeparator as string=";"
Dim keyValSeparator as string="=";
Dim allPersons As New Dictionary(Of String, Person)
Dim str As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim parts As New List(Of String)(str.Split(persSeparator.ToCharArray)) 'why dont want you to split this string??
Dim person As New Person
For Each part As String In parts
Dim keyValue() As String = part.Split(keyValSeparator.toCharArray())
Select Case keyValue(0).ToUpper
Case "ID"
person.ID = keyValue(1)
Case "NAME"
person.Name = keyValue(1)
Case "BIRTHDAY"
person.BirthDay= keyValue(1)
End Select
Next
If Not allPersons.ContainsKey(person.ID) Then
allPersons.Add(person.ID, person)
End If
Public Class Person
Private _name As String
Private _birthday As String
Private _id As String = String.Empty
Public Sub New()
End Sub
Public Sub New(ByVal id As String)
Me._id = id
End Sub
Public Sub New(ByVal id As String, ByVal name As String)
Me._id = id
Me._name = name
End Sub
Public Sub New(ByVal id As String, ByVal name As String, ByVal birthday As String)
Me._id = id
Me._name = name
Me._birthday = birthday
End Sub
Public Property ID() As String
Get
Return Me._id
End Get
Set(ByVal value As String)
Me._id = value
End Set
End Property
Public Property Name() As String
Get
Return Me._name
End Get
Set(ByVal value As String)
Me._name = value
End Set
End Property
Public Property BirthDay() As String
Get
Return Me._birthday
End Get
Set(ByVal value As String)
Me._birthday = value
End Set
End Property
Public Overrides Function Equals(ByVal obj As Object) As Boolean
If TypeOf obj Is Person AndAlso Not obj Is Nothing Then
Return String.Compare(Me._id, DirectCast(obj, Person).ID) = 0
Else : Return False
End If
End Function
End Class
If you were just wanting to extract the birthday and ID from the string and place as a value pair in some sort of dictionary, for simplicity I would use regular expressions and then a generic dictionary (of string, valuepair structure). Something like this:
Imports System.Text.RegularExpressions
Imports System.Collections.Generic
Sub Main()
Dim Person As New Dictionary(Of String, ValuePair)
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12"
Dim r As Regex = New Regex("Name=(.*);Birthday=(.*);ID=(.*$)")
Dim m As Match = r.Match(s)
Person.Add(CStr(m.Groups(1).Value), _
New ValuePair(CDate(m.Groups(2).Value), CInt(m.Groups(3).Value)))
Console.WriteLine(Person("Fred").Birthday.ToString)
Console.WriteLine(Person("Fred").ID.ToString)
Console.Read()
End Sub
Friend Structure ValuePair
Private _birthday As Date
Private _ID As Int32
Public ReadOnly Property ID() As Int32
Get
Return _ID
End Get
End Property
Public ReadOnly Property Birthday() As Date
Get
Return _birthday
End Get
End Property
Sub New(ByVal Birthday As Date, ByVal ID As Int32)
_birthday = Birthday
_ID = ID
End Sub
End Structure