3

我正在查找 a 中的键,Scripting.Dictionary以确保我只将它们(及其项目)添加到字典中一次:

If MyDict.Exists (Key) Then ' first internal key-value lookup
  Set Entry=MyDict.Item (Key)  ' Second internal key->value lookup
else
  Set Entry=New ItemType
  MyDict.Add Key,Entry
End If
' Now I work on Entry...

Exists在字典中查找键,并且Item ()也这样做。因此,我为一个逻辑查找操作获得了两个关键查找。没有更好的方法吗?

Item物业的 dox 说

“如果在尝试返回现有项目时未找到密钥,则会创建一个新密钥并将其对应的项目留空。” ( MSDN )

这是真的,即查找不存在的键显然会使该键成为字典的一部分,可能与关联的 item = 空。但这有什么好处呢?我怎么能用它把它归结为一个查找操作?Item ()在属性调用期间创建密钥后如何设置空项?

4

2 回答 2

2

This code and output:

>> Set d = CreateObject("Scripting.Dictionary")
>> WScript.Echo 0, d.Count
>> If d.Exists("soon to come") Then : WScript.Echo 1, d.Count : End If
>> WScript.Echo 2, d.Count
>> d("soon to come") = d("soon to come") + 1
>> WScript.Echo 3, d.Count, d("soon to come")
>>
0 0
2 0
3 1 1

shows:

  1. Looking up a non existing key with .Exists does not add the key to the dictionary (.Count is still 0 at #2)
  2. Accessing a non existing key via .Item or () - as on the right hand side of the assignment in my sample code - adds a key/Empty pair to the dictionary; for some task (e.g. frequency counting) this 'works', because Empty is treated as 0 in addition or "" in string concatenation. This small scale autovivification can't be used for objects (no decent way to map Empty to whatever object the programmer thinks off and no default magic as in Python or Ruby in VBScript)
  3. If you have to maintain a dictionary of named objects and can access the name and its object at the same time, you can just write Set d(name) = object - d(name) will create the key slot "name" if necessary and the Set assignment will put the object into the corresponding value (overwriting Empty or the 'old' object ('pointer')).

If you append some details about what you really want to achieve, I'm willing to add to this answer.

Added:

If you (logically) work on a list of keys with duplicates and must add new objects to the dictionary on the fly, you can't avoid the double lookup, because you need to check for existence (1.0) and assign (2.0) the (perhaps newly created and assigned(1.5)) object to your work variable (see /m:a or /m:b in my sample code). Other languages with statements that deliver a value may allow something like

if ! (oBJ = dicX( key )) {
   oBJ = dicX( key ) = new ItemType() 
}
oBJ.doSomething()

and without VBScript's Set vs. Let abomination something like

oBJ = dicX( key )
If IsEmpty( oBJ ) Then
   dicX( key ) = New ItemType
   oBJ = dicX( key ) 
End If

would do the extra work for new elements only, but all that is a pipe dream.

If those double lookups really matter (which I doubt - can you give an argument or evidence?), then the overall design of your program does matter. For example: If you can unique-fy your work list, everything becomes simple (see /m:c in my sample). Admittedly, I still have no idea, whether such changes are possible for your specific task.

Code to experiment with:

Dim dicX  : Set dicX = CreateObject( "Scripting.Dictionary" )
Dim aKeys : aKeys    = Split( "1 2 3 4 4 3 2 1 5" )
Dim sMode : sMode    = "a"
Dim oWAN  : Set OWAN = WScript.Arguments.Named
If oWAN.Exists( "m" ) Then sMode = oWAN( "m" )
Dim sKey, oBJ
Select Case sMode
  Case "a"
    For Each sKey In aKeys
      If Not dicX.Exists( sKey ) Then 
         Set dicX( sKey ) = New cItemType.init( sKey )
      End If
      Set oBJ = dicX( sKey )
      WScript.Echo oBJ.m_sInfo
    Next    
  Case "b"  
    For Each sKey In aKeys
      If IsEmpty( dicX( sKey ) ) Then 
         Set dicX( sKey ) = New cItemType.init( sKey )
      End If   
      Set oBJ = dicX( sKey )
      WScript.Echo oBJ.m_sInfo
    Next    
  Case "c"  
    aKeys = uniqueList( aKeys )
    For Each sKey In aKeys
      Set dicX( sKey ) = New cItemType.init( sKey )
      Set oBJ = dicX( sKey )
      WScript.Echo oBJ.m_sInfo
    Next    
  Case Else
    WScript.Echo "Unknown /m:" & sMode & ", pick one of a, b, c."
End Select
WScript.Echo "----------"
For Each sKey In dicX.Keys
    WScript.Echo dicX( sKey ).m_sInfo
Next

Dim g_ITCnt : g_ITCnt = 0
Class cItemType
  Public m_sInfo
  Public Function init( sKey )
    Set init = Me
    g_ITCnt  = g_ITCnt + 1
    m_sInfo  = "Obj for " & sKey & " (" & g_ITCnt & ")"
  End Function   
End Class ' cItemType

Function uniqueList( aX )
  Dim dicU : Set dicU = CreateObject( "Scripting.Dictionary" )
  Dim vX
  For Each vX in aX
      dicU( vX ) = Empty
  Next    
  uniqueList = dicU.Keys
End Function

sample output:

/m:a
Obj for 1 (1)
Obj for 2 (2)
Obj for 3 (3)
Obj for 4 (4)
Obj for 4 (4)
Obj for 3 (3)
Obj for 2 (2)
Obj for 1 (1)
Obj for 5 (5)
----------
Obj for 1 (1)
Obj for 2 (2)
Obj for 3 (3)
Obj for 4 (4)
Obj for 5 (5)
==================================================
xpl.vbs: Erfolgreich beendet. (0) [0.07031 secs]

/m:c
Obj for 1 (1)
Obj for 2 (2)
Obj for 3 (3)
Obj for 4 (4)
Obj for 5 (5)
----------
Obj for 1 (1)
Obj for 2 (2)
Obj for 3 (3)
Obj for 4 (4)
Obj for 5 (5)
================================================
xpl.vbs: Erfolgreich beendet. (0) [0.03906 secs]

The timing difference is probably caused by the reduced output of the /m:c mode, but it emphasizes the importance of not doing something more often then necessary.

于 2011-08-02T12:15:46.453 回答
1

对我来说,关键问题是 VBScript 强制我们使用Set对象,而Empty不是对象。我过去用来解决这个问题的一个技巧是使用该Array函数为该值创建一个临时占位符。然后我可以检查数组以查看该值是否为对象。应用于您的示例:

tempArr = Array(dict.Item(key))
If IsEmpty(tempArr(0)) Then
    ' new entry
    Set entry = New MyClass
    ' can't use Add because the key has already been implicitly created
    Set dict.Item(key) = entry
Else
    ' existing entry
    Set entry = tempArr(0)
End If

但是,在这种情况下,您并没有消除双重查找,而是将其从“现有条目”案例移至“新条目”案例。

于 2011-08-03T04:57:38.397 回答