tags:

views:

164

answers:

7

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?

+2  A: 

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.

ho1
What does `c` in `"c)` mean?
abatishchev
@abatishchev: That it's character rather than a string, since you can't write ' for characters in VB.
ho1
Also you can use `Split("=")` again (why not?) instead of `IndexOf`/`Substring`
abatishchev
@abatishchev: Just because he said that he didn't want to split so I thought I'd minimize the number of splits since it's not that much more work to use IndexOf (except that I always get off by one errors :)).
ho1
Thanks! Will know now. As far as I could understand this is an analog of `CType("s", Char)`, right?
abatishchev
@abatischev: Not really. It's like `'c'` in C#; i.e., it's a literal of type `Char`. `CType("s", Char)` would be more analogous to `Convert.ToChar("s")` -- *converting* a `string` *to* a `char`.
Dan Tao
@abatischev: What Dan said... you can find the spec for them here http://msdn.microsoft.com/en-us/library/aa711652%28VS.71%29.aspx (the proper name is apparently "Character literal")
ho1
+2  A: 

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

abatishchev
Try compiling this with `Option Strict On` – it will fail (here’s a hint: **always** use `Option Strict On`!). The reason is that the one-parameter overload of `Split` only takes a single char, not a string.
Konrad Rudolph
@Konrad: Right! Need to use `c`-suffix
abatishchev
A: 
 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
Tim Schmelter
Probably in `Case "BIRTHDAY" person.Name = keyValue(1)` you meant `person.Birthday = ...`
abatishchev
Already changed, that happens when you dont test your code ;)Also changed type from BirthDay to String.
Tim Schmelter
A: 

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
knslyr
+1  A: 

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]);
Konrad Rudolph
How would it look like in c# ?
abatishchev
@abatishchev: see my updated answer. To paraphrase, “almost identical.” ;-)
Konrad Rudolph
+1  A: 

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.

Calgary Coder
Why not use `Matches` so the result can be iterated properly using `foreach`? Just using the pedestrian `Match` has virtually no advantage.
Konrad Rudolph
I was focused on the Regular Expression over the consumption of results. Matches would allow for the use of ForEach as you have indicated (and is definitely cleaner), however, in terms of "no advantage", I disagree as they both use the same Run method internally (i.e., cost wise they would be the same). So regardless of Match or Matches, each method only does a single pass over the string.
Calgary Coder
@Calgary: that’s why I said “has no advantage” rather than “has disadvantages” and to be honest, I find it odd to prefer the `While …NextMatch()` syntax over the idiomatic `For each`. But still, this is a very good solution.
Konrad Rudolph
Fair enough and thanks. Normally I would go the ForEach as well (and I probably would have refactored it if it was code I was going to use). Regardless, thank you for the catch!
Calgary Coder