Scripting.Dictionary Lookup-add-if-not-present только с одним поиском ключа?

Я ищу ключи в 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 () делает это тоже. Таким образом, я получаю два ключевых поиска для одной логической операции поиска. Разве нет лучшего способа?

Dox для Item собственность сказать

"Если ключ не найден при попытке вернуть существующий элемент, создается новый ключ и соответствующий ему элемент остается пустым". ( MSDN)

Это действительно так, то есть поиск несуществующего ключа, очевидно, делает этот ключ частью словаря, возможно, с соответствующим item = empty. Но для чего это нужно? Как я мог использовать это, чтобы свести это к одной операции поиска? Как я могу установить пустой элемент после создания ключа во время Item () вызов недвижимости?

2 ответа

Решение

Ключевой проблемой для меня является тот факт, что 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

В этом случае, однако, вы не исключили двойной поиск, а переместили его из дела "существующая запись" в дело "новая запись".

Этот код и вывод:

>> 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

показывает:

  1. Поиск несуществующего ключа с помощью.Exists не добавляет ключ в словарь (.Count по-прежнему равен 0 в #2)
  2. Доступ к несуществующему ключу через.Item или () - как в правой части назначения в моем примере кода - добавляет пару ключ / пусто в словарь; для некоторой задачи (например, подсчет частоты) это "работает", потому что Empty обрабатывается как 0 дополнительно или "" в конкатенации строк. Эту мелкомасштабную автовивификацию нельзя использовать для объектов (нет приличного способа сопоставления Empty с любым объектом, который придумывает программист, и никакой магии по умолчанию, как в Python или Ruby в VBScript)
  3. Если вам нужно вести словарь именованных объектов и одновременно иметь доступ к имени и его объекту, вы можете просто написать Set d(name) = object - d(name) создаст ключевой слот "name", если необходимо, а назначение Set поместит объект в соответствующее значение (перезаписывает пустой или "старый" объект ("указатель")).

Если вы добавите некоторые подробности о том, чего вы действительно хотите достичь, я хочу добавить к этому ответу.

Добавлено:

Если вы (логически) работаете со списком ключей с дубликатами и должны добавлять новые объекты в словарь на лету, вы не можете избежать двойного поиска, потому что вам нужно проверить наличие (1.0) и назначить (2.0) (возможно, недавно созданный и назначенный (1.5)) объект вашей рабочей переменной (см. /m:a или /m:b в моем примере кода). Другие языки с операторами, которые предоставляют значение, могут разрешать что-то вроде

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

и без набора VBScript против пусть мерзость что-то вроде

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

будет делать дополнительную работу только для новых элементов, но все это несбыточная мечта.

Если эти двойные поиски действительно имеют значение (в чем я сомневаюсь - можете ли вы привести аргумент или доказательство?), Тогда общий дизайн вашей программы имеет значение. Например: если вы можете однозначно определить свой рабочий список, все становится просто (см. / M:c в моем примере). Правда, я до сих пор не знаю, возможны ли такие изменения для вашей конкретной задачи.

Код для экспериментов:

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

образец вывода:

/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]

Разница во времени, вероятно, вызвана уменьшением вывода в режиме / m:c, но это подчеркивает важность того, чтобы не делать что-то чаще, чем необходимо.

Другие вопросы по тегам