Метод VBA Dictionary.Exists, возвращающий ложное отрицание данной строки из массива строк - PullRequest
1 голос
/ 11 июня 2019

Я передаю ключ типа string, расположенный в строковом массиве, в метод scripting.dictionary.exists (key), который возвращает ложный минус.Я хотел бы, чтобы он правильно возвращал положительное логическое значение, чтобы я мог определить, является ли ключ действительным для бизнес-процесса.Допустимые значения хранятся в словаре.

Я проверил, чтобы режим сравнения словаря был TextCompare, который не имел бы значения, потому что оказалось, что регистр строки в обоих словаряхи массив идентичны.Я также позаботился о том, чтобы оба типа переменных были string (8).Кроме того, я убедился, что строка в массиве строк и словаре идентичны.Наконец, я проверил, чтобы метод также возвращал ложное срабатывание, если строка вводилась непосредственно в аргумент, а не передавалась в аргумент через ссылку через массив.

Function fvalidatedata(saInput() As String) As String()

    Dim dInvalidRecords As Scripting.Dictionary, _
        dValidDisputeStatuses As Scripting.Dictionary, dDisputeStatusErrorMap As Scripting.Dictionary, _
        dValidReasonCodes As Scripting.Dictionary, dReasonCodeAbbrevMap As Scripting.Dictionary, _
        saListBoxValues() As String, i As Long, ii As Long, frmIRC As New ufInvalidReasonCode

    Set dInvalidRecords = New Scripting.Dictionary
    Set dValidDisputeStatuses = New Scripting.Dictionary
    Set dDisputeStatusErrorMap = New Scripting.Dictionary
    Set dValidReasonCodes = New Scripting.Dictionary
    Set dReasonCodeAbbrevMap = New Scripting.Dictionary

    dInvalidRecords.CompareMode = BinaryCompare
    dValidDisputeStatuses.CompareMode = TextCompare
    dDisputeStatusErrorMap.CompareMode = TextCompare
    dValidReasonCodes.CompareMode = BinaryCompare
    dReasonCodeAbbrevMap.CompareMode = BinaryCompare

    Set dValidDisputeStatuses = fValidDisputeStatusMap
    Set dDisputeStatusErrorMap = fDisputeStatusErrorMap
    Set dValidReasonCodes = fValidReasonCodes
    Set dReasonCodeAbbrevMap = fReasonCodeAbbrevMap

    For i = 2 To UBound(saInput, 1)
        'Dispute Status Validation
            If Not dValidDisputeStatuses.Exists(saInput(i, 12)) Then
                Debug.Print dValidDisputeStatuses.Exists(saInput(i, 12))
                If dDisputeStatusErrorMap.Exists(saInput(i, 12)) Then
                    saInput(i, 12) = dDisputeStatusErrorMap(saInput(i, 12))
                Else
                    ReDim saListBoxValues(0 To dValidDisputeStatuses.Count - 1)
                    For ii = 0 To dValidDisputeStatuses.Count - 1
                        saListBoxValues(ii) = dValidDisputeStatuses.Keys(ii)
                    Next ii
                    frmIRC.ListBox1.List = saListBoxValues
                    frmIRC.l1 = "Please select valid dispute status from the list below for record " & saInput(i, 3) & " and submit once complete."
                    frmIRC.Show vbModeless

                End If
            End If
        'Reason Code Validation
    Next i

End Function
Function fValidDisputeStatusMap() As Scripting.Dictionary

    Dim dMap As Scripting.Dictionary, lo As ListObject, i As Long

    Set dMap = New Scripting.Dictionary
    dMap.CompareMode = TextCompare

    Set lo = Application.Workbooks("RnR_Dispute_Process_Workbook.xlsx").Sheets("Update Dictionary").ListObjects("Valid_Statuses")

    For i = 1 To lo.ListColumns("Valid Statuses").DataBodyRange.Count
        If Not dMap.Exists(lo.ListColumns("Valid Statuses").DataBodyRange(i)) Then
            dMap.Add lo.ListColumns("Valid Statuses").DataBodyRange(i), vbNullString
        End If
    Next i

    Set fValidDisputeStatusMap = dMap

    Set dMap = Nothing

End Function

Я ожидаю, что выводиз (не dValidDisputeStatuses.Exists (saInput (i, 12))) в значение TRUE, если saInput (i, 12) не существует в словаре, но он существует.

Ответы [ 2 ]

2 голосов
/ 11 июня 2019

Я передаю ключ типа string

Но вы не :) Вы фактически передаете объект Range здесь:

If Not dMap.Exists(lo.ListColumns("Valid Statuses").DataBodyRange(i))

Случается, что в большинстве случаев (например, при присваивании - a Range) объект Range возвращает свое свойство по умолчанию (Cells), которое возвращаетего свойство по умолчанию (Value), но когда получает Range, это не так надежно.

Вы наткнулись на интересный случай!Хотя Keys в словаре может на что угодно , кроме массива, как вы невольно заметили, могут возникнуть странные вещи, когда вы используете сложные объекты как Keys.

A Dictionary объект является эквивалентом ассоциативного массива PERL.Элементы, которые могут быть любой формы данных, хранятся в массиве.Каждый элемент связан с уникальным ключом.Ключ используется для извлечения отдельного элемента и обычно представляет собой целое число или строку, но может быть чем угодно, кроме массива.

Вот странность: каждый раз, когда вы ссылаетесь на объект Range, этоучитывая другое место в памяти.Вы можете легко это проверить:

Dim i as Long
Dim r as Range
For i = 1 to 3
    Set r = Range("A1")
    Debug.Print(ObjPtr(r))
Next

В вашем случае это означает, что метод Exists будет явно терпеть неудачу (т.е. возвращает False, когда он, по-видимому, должен вернутьсяTrue), например, расширив вышесказанное:

Dim i As Long
Dim r As Range
Dim d as Object
Set d = CreateObject("Scripting.Dictionary")
For i = 1 to 3
    Set r = Range("A1")
    d.Add r, i
Next

Это даст словарь с 3 уникальными ключами, которые все являются указателями на один и тот же Range объект!

Если вместо этого присваивание Set равно за пределами цикла, оно завершится с ошибкой, как и ожидалось на второй итерации, поскольку теперь он пытается добавить указатель, который уже существует в словаре.

Set r = Range("A1")
For i = 1 to 3
    d.Add r, i
Next

Мораль этой истории такова: будьте осторожны при назначении Object типов как Keys в Dictionary.

Можем ли мы использовать Object типов как Dictionary.Keys?

Да, но это, кажется, более сложная реализация.Могут быть и другие способы, но одно очевидное (по крайней мере для меня) решение - сначала создать ключи как массив или коллекцию, а затем выполнить итерацию по этому списку во время тестирования Dictionary.Exists.

Sub foo2()

Dim d As Dictionary
Dim r As Range, w As Worksheet
Dim i As Long
Dim keys(1 To 3) As Range
Set d = CreateObject("Scripting.Dictionary")

Set r = Range("A1:A3")
' Create our keys in one place
For i = 1 To r.Cells.Count
    Set keys(i) = r.Cells(i)
Next

' Iterate the KEYS rather than the range
For i = LBound(keys) To UBound(keys)
    d.Add keys(i), i
Next
Debug.Print "The dictionary initially contains " & d.Count & " keys."
'If we test Exist against our keys array, results are as expected:
For i = LBound(keys) To UBound(keys)
    If Not d.Exists(keys(i)) Then
        d.Add r.Cells(i), i
    End If
Next
Debug.Print "The dictionary still contains " & d.Count & " keys."

' Iterate the RANGE now and no error occurs!
For i = 1 To r.Cells.Count
    If Not d.Exists(r.Cells(i)) Then
        d.Add r.Cells(i), i
    End If
Next
' But, our dictionary now has 6 keys, instead of 3!!!
Debug.Print "The dictionary now contains " & d.Count & " keys!"
End Sub


1 голос
/ 11 июня 2019

Я понял:

For i = 1 To lo.ListColumns("Valid Statuses").DataBodyRange.Count
    If Not dMap.Exists(lo.ListColumns("Valid Statuses").DataBodyRange(i)) Then
        dMap.Add lo.ListColumns("Valid Statuses").DataBodyRange(i), vbNullString
    End If
Next i

должно было быть

For i = 1 To lo.ListColumns("Valid Statuses").DataBodyRange.Count
    If Not dMap.Exists(lo.ListColumns("Valid Statuses").DataBodyRange(i).value2) Then
        dMap.Add lo.ListColumns("Valid Statuses").DataBodyRange(i).value2, vbNullString
    End If
Next i

Я ссылался на диапазон вместо значения.Очевидно, vartype () проверяет значение внутри диапазона с учетом значения диапазона.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...