Scripting.Dictionary Lookup-add-if-not-present только с одним поиском ключа? - PullRequest
2 голосов
/ 02 августа 2011

Я ищу ключи в 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 ()?

Ответы [ 2 ]

2 голосов
/ 02 августа 2011

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

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

и без Set VBScript против Let abomination что-то вроде

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, но это подчеркивает важность того, чтобы не делать что-то чаще, чем необходимо.

0 голосов
/ 03 августа 2011

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

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

...