Two-Way Dictionary

A couple of years ago, @Emile Schwarz asked about a two-way dictionary: https://forum.xojo.com/51746-dictionary-how-do-i-get-the-value. As the title of that post was a bit ambiguous I decided to make a new one for the benefit of future seekers.

As @Jeff Tullin pointed out in that thread, two-way, or “reverse-lookup” is problematic if keys and values are not unique, as the results will be ambiguous. Nonetheless, I find the functionality very useful in certain cases where the key:value pairs are well-defined beforehand and don’t contain any duplications. My particular use case at this time involves hardware whose parameters are controlled by integer values unrelated to the actual values they manifest, for example a programmable-gain amplifier whose gain might be 1, 10, 20, or 50, as determined by control values of 0,1,2,and 3. While there are other ways to do this (as always), I find It convenient and clean in my Xojo apps to be able to store these equivalencies in just one property where I can look up the real value from the control value and vice-versa. So FWIW here’s the code, which turns out to be rather trivial:

[code]Class DictionaryTwoWay

Sub Constructor(ParamArray Key_Values() As Pair)

LR = New Dictionary
RL = New Dictionary
For Each p As Pair In Key_Values
LR.Value(p.Left) = p.Right
RL.Value(p.Right) = p.Left
Next
End Sub

Function Value(Key As Variant) As Variant
If LR.HasKey(Key) Then
Return LR.Value(Key)
Elseif RL.HasKey(Key) Then
Return RL.Value(Key)
End
End Function

Sub Value(Key As Variant, assigns Val as Variant)
LR.Value(Key) = Val
RL.Value(Val) = Key
End Sub

Function HasKey(Key as Variant) as Boolean
If LR.HasKey(Key) Then Return True
If RL.HasKey(Key) Then Return True
End Function

Property
Private LR As Dictionary
EndProperty

Property
Private RL As Dictionary
EndProperty

End Class
[/code]

Usage is the same as a standard dictionary. It’s up to the developer to ensure uniqueness of keys and values, but if there are duplicates no exception is raised, it simply returns the value corresponding to the first key it finds. If it doesn’t find a key it returns nil, but there is a HasKey function just like the standard dictionary.

Oh, I see now my example was bad, because “1” is present in both the keys and values, lol. But you get the idea :slight_smile:

A couple of thoughts:

  1. Couldn’t you use one Dictionary for this?
Sub Value( key, Assigns value )
  dict.Value( key ) = value
  if key <> value then
    dict.Value( value ) = key
  end if
  1. In the case of key1 = valueN, then later key2 = valueN, key1 will be orphaned with the two-Dictionary scheme.

I keep thinking one ‘solution’ to this problem is an in-memory database

where thetable has 2 columns: KEY / VALUE

A bit like this…

[code]function ValueFromKey(thekey ) as variant
// select Value from table where key = thekey
//return value
end function

function keyFromValue(thevalue ) as variant
// select Key from table where value = thevalue
//return key
end function[/code]

[quote=483920:@Kem Tekinay]A couple of thoughts:

  1. Couldn’t you use one Dictionary for this?
Sub Value( key, Assigns value )
  dict.Value( key ) = value
  if key <> value then
    dict.Value( value ) = key
  end if
  1. In the case of key1 = valueN, then later key2 = valueN, key1 will be orphaned with the two-Dictionary scheme.[/quote]

  2. Probably, but I think in retrieval if what you’re using as a key is not found in Keys then you’d have to iterate through the dictionary looking for it in the values. With two dictionaries you can just use HasKey twice and done.

  3. Yeah, my usage for this structure is pretty much always with hard-coded fixed dictionary contents. Orphans and ambiguity are definitely a danger if adding pairs on the fly.

both of those issues are solvable so you can have a many to many map

Some miscommunication here, I think. My idea was that you would assign to the same Dictionary both key = value, and value = key. When checking, you’d only need HasKey once.

You are so smart! :smiley:

The disadvantage to that would be an ugly instantiation, as you couldn’t just do

MUXcodes = New DictionaryTwoWay(_ "DUTdiff":"0000",_ "DUT+ - Vdd":"0001",_ "DUT- -Vdd":"0002",_ "Current":"0003",_ "DUT+":"0004",_ "DUT-":"0005",_ "Vref":"0006",_ "Vdd":"0007")

  • you’d have to put all the pairs in twice, making it longer, less readable, and adding potential for human error.

[quote=483931:@Julia Truchsess]You are so smart! :smiley:

  • you’d have to put all the pairs in twice, making it longer, less readable, and adding potential for human error.[/quote]

Humans make errors??? :wink:

I think I missed something. Why would you have to do that?

Oh, duh, of course, the swapping and storing of reversed pairs would be done by the Constructor. Sorry, multitasking.

Actually, it would be done by the overridden Value method, as I posted above.

When assigning a value via the Value method, yes. My use case generally never uses the Value method to assign values; assignment is done via the Constructor as shown in my post a little while ago, because all the keys and values are known at compile time.

So we’d update it like this:

Sub Constructor(ParamArray Key_Values() As Pair)
  MyDict = new Dictionary

  For Each p As Pair In Key_Values
    self.Value( p.Left ) = p.Right
  Next
End Sub

Sub Value(key As Variant, Assigns value As Variant)
  MyDict.Value( key ) = value
  MyDict.Value( value ) = key
End Sub

[quote=483945:@Kem Tekinay]So we’d update it like this:

[code]
Sub Constructor(ParamArray Key_Values() As Pair)
MyDict = new Dictionary

For Each p As Pair In Key_Values
self.Value( p.Left ) = p.Right
Next
End Sub

Sub Value(key As Variant, Assigns value As Variant)
MyDict.Value( key ) = value
MyDict.Value( value ) = key
End Sub
[/code][/quote]

But then the right-to-left pairs only ever get initialized via the Value(key As Variant, Assigns value As Variant) subroutine. I’m saying I typically would never use that subroutine; I would initialize the dict via Constructor and then only ever use the Value Function.

So this is a read only concept?

That’s my primary use case / need at present, but I’m thinking it could be handy in other cases too.