Установить структуру данных в VBA

Я ищу структуру данных set для использования в Excel VBA. На данный момент я нашел Scripting.Dictionary, который выглядит как карта.

В VBA тоже есть что-то вроде набора?

В основном я ищу структуру данных, которая эффективна для определения того, было ли уже добавлено определенное значение.


person Sandro    schedule 18.01.2017    source источник
comment
Возможно, вы имели в виду Коллекция   -  person Storax    schedule 18.01.2017
comment
может массив? Или коллекция? Или класс?   -  person Pierre    schedule 18.01.2017
comment
Я пытаюсь найти дубликаты. Насколько я понимаю, массивы и коллекции или не индексируются, поэтому мне пришлось бы пройти все это, чтобы узнать, присутствует ли уже определенное значение.   -  person Sandro    schedule 18.01.2017
comment
По определению набор не может содержать дубликатов, верно?   -  person Storax    schedule 18.01.2017
comment
Точно и обычно у него есть эффективный способ гарантировать это и метод проверки наличия значения. Это то, что я хочу использовать для поиска повторяющихся строк на листе.   -  person Sandro    schedule 18.01.2017
comment
Возможно, вы посмотрите здесь excelfunctions.net/find-duplicate-rows.html.   -  person Storax    schedule 18.01.2017
comment
Ключи Scripting.Dictionary уникальны, метод Exists позволяет проверить, есть ли уже ключ в словаре.   -  person omegastripes    schedule 18.01.2017
comment
@omegastripes Это то, что я сейчас пытаюсь сделать, спасибо. Поскольку мне нужен только ключ, а не значение, я подумал, что словарь может быть излишним.   -  person Sandro    schedule 18.01.2017
comment
@Storax Спасибо за идею. Я пытаюсь сделать это программно, поэтому в моем случае это не работает.   -  person Sandro    schedule 18.01.2017
comment
@Sandro Вы хотите выделить повторяющиеся ячейки в столбце или получить список различных (или список повторяющихся) значений из столбца?   -  person omegastripes    schedule 18.01.2017
comment
@omegastripes Я объединяю ячейки каждой строки и хочу найти одинаковые. Пока это проблема программирования, про презентацию пока не знаю.   -  person Sandro    schedule 18.01.2017
comment
@Sandro Dictionary на самом деле довольно быстрый, и это хеш-таблица, которая является правильной реализацией для набора. Лично я бы просто написал оболочку для Dictionary, которая раскрывает нужные вам методы. Я добавлю ответ для вас.   -  person Blackhawk    schedule 19.01.2017
comment
@Sandro Я написал класс HashSet с нуля, но для ваших целей это, вероятно, излишне. Он использует динамический массив, который растет по мере добавления элементов, с коэффициентом загрузки по умолчанию 75% для минимизации коллизий. Столкновения обрабатываются с помощью связанных списков. Если вам интересно, дайте мне знать - во многих случаях это быстрее, чем Scripting.Dictionary, потому что он использует ASCII вместо Unicode - я использую djb2 для хеширования строк.   -  person Blackhawk    schedule 19.01.2017
comment
Отвечая на вопрос, взгляните на .NET ArrayList, вы можете создать экземпляр объекта в среде VBS и VBA Set ArrayList = CreateObject("System.Collections.ArrayList"), он имеет такие методы, как Add, Contains и Sort.   -  person omegastripes    schedule 20.01.2017
comment
@ZevSpitz Спасибо, очень информативно! Особенно первый.   -  person Sandro    schedule 20.01.2017
comment
@omegastripes Спасибо за предложение. Проблема в том, что строк будет очень много. ArrayList#contains неэффективен, из документа: This method performs a linear search; therefore, this method is an O(n) operation, where n is Count. Поэтому, если я использую его для поиска всех повторяющихся строк, у меня будет O (n ^ 2), а не O (n) при использовании HashSet. Кроме того, я думаю, это возможно только при установленном .NET.   -  person Sandro    schedule 20.01.2017


Ответы (3)


Взгляните на .NET ArrayList, у него есть такие методы, как Add, Contains, Sort и т. д. Вы можете создать экземпляр объекта в среде VBS и VBA:

Set ArrayList = CreateObject("System.Collections.ArrayList")

Scripting.Dictionary тоже может подойти, у него уникальные ключи, метод Exists позволяет проверить, есть ли уже ключ в словаре.

Однако SQL-запрос через ADODB, вероятно, будет более эффективным в этом случае. В приведенных ниже примерах показано, как получить уникальные строки с помощью SQL-запроса к рабочему листу:

Option Explicit

Sub GetDistinctRecords()

    Dim strConnection As String
    Dim strQuery As String
    Dim objConnection As Object
    Dim objRecordSet As Object

    Select Case LCase(Mid(ThisWorkbook.Name, InStrRev(ThisWorkbook.Name, ".")))
        Case ".xls"
            strConnection = "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source='" & ThisWorkbook.FullName & "';Mode=Read;Extended Properties=""Excel 8.0;HDR=YES;"";"
        Case ".xlsm", ".xlsb"
            strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source='" & ThisWorkbook.FullName & "';Mode=Read;Extended Properties=""Excel 12.0 Macro;HDR=YES;"";"
    End Select

    strQuery = "SELECT DISTINCT * FROM [Sheet1$]"
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Open strConnection
    Set objRecordSet = objConnection.Execute(strQuery)
    RecordSetToWorksheet Sheets(2), objRecordSet
    objConnection.Close

End Sub

Sub RecordSetToWorksheet(objSheet As Worksheet, objRecordSet As Object)

    Dim i As Long

    With objSheet
        .Cells.Delete
        For i = 1 To objRecordSet.Fields.Count
            .Cells(1, i).Value = objRecordSet.Fields(i - 1).Name
        Next
        .Cells(2, 1).CopyFromRecordset objRecordSet
        .Cells.Columns.AutoFit
    End With

End Sub

Исходные данные помещаются на Sheet1, результат выводится на Sheet2. Единственное ограничение для этого метода заключается в том, что ADODB подключается к книге Excel на диске, поэтому любые изменения должны быть сохранены перед запросом, чтобы получить фактические результаты.

Если вы хотите получить только набор неразличимых строк, то запрос должен быть следующим (просто пример, вы должны поместить свой набор полей в запрос):

    strQuery = "SELECT CustomerID, CustomerName, ContactName, Address, City, PostalCode, Country FROM [Sheet1$] GROUP BY CustomerID, CustomerName, ContactName, Address, City, PostalCode, Country HAVING Count(*) > 1"
person omegastripes    schedule 18.01.2017
comment
Вау, я не знал, что в Excel можно выполнять SQL-запросы, здорово! В моем случае это должно работать и без сохранения, так как я использую его для проверки. Это как-то возможно с этим методом? Я думаю, что не могу принять этот ответ, так как прошу заданную структуру данных. Если бы это был принятый ответ, это могло бы ввести в заблуждение людей, ищущих структуру данных. - person Sandro; 18.01.2017
comment
@Sandro, не могли бы вы уточнить, какая проверка вам нужна? Как должны отображаться допустимые и недействительные строки? - person omegastripes; 19.01.2017
comment
Ваш ответ, похоже, делает то, что я пытался сделать, за исключением того, что сначала нужно сохранить. Пока речь идет только о проверке, как будут отображаться строки, еще не решено. До сих пор я прибегал к использованию Scripting.Dictionary, это не набор, но он выполняет свою работу. - person Sandro; 20.01.2017

Вы можете использовать коллекцию и выполнить следующую функцию: коллекции применяют уникальные идентификаторы ключей:

Public Function InCollection(Col As Collection, key As String) As Boolean
  Dim var As Variant
  Dim errNumber As Long

  InCollection = False
  Set var = Nothing

  Err.clear
  On Error Resume Next
    var = Col.Item(key)
    errNumber = CLng(Err.Number)
  On Error GoTo 0

  '5 is not in, 0 and 438 represent incollection
  If errNumber = 5 Then ' it is 5 if not in collection
    InCollection = False
  Else
    InCollection = True
  End If

End Function
person Zerk    schedule 18.01.2017
comment
Спасибо за подробный ответ! В чем разница/преимущество использования Scripting.Dictionary. Использование словаря кажется более интуитивным, так как он имеет метод Exists. Я искал другую структуру данных, потому что и коллекция, и словарь кажутся структурами ключ-значение, мне нужен только ключ. - person Sandro; 18.01.2017
comment
Это хороший источник информации о коллекциях и словарях (experts-exchange.com/articles/3391/). На самом деле оба прекрасно подходят для этой цели, поэтому выберите тот, который более удобен. - person Zerk; 18.01.2017

Просто напишите оболочку для Scripting.Dictionary, которая предоставляет только операции, подобные наборам.

клссет

Option Explicit

Private d As Scripting.Dictionary

Private Sub Class_Initialize()
    Set d = New Scripting.Dictionary
End Sub

Public Sub Add(var As Variant)
    d.Add var, 0
End Sub

Public Function Exists(var As Variant) As Boolean
    Exists = d.Exists(var)
End Function

Public Sub Remove(var As Variant)
    d.Remove var
End Sub

И тогда вы можете использовать его так:

mdlMain

Public Sub Main()
    Dim s As clsSet
    Set s = New clsSet

    Dim obj As Object

    s.Add "A"
    s.Add 3
    s.Add #1/19/2017#

    Debug.Print s.Exists("A")
    Debug.Print s.Exists("B")
    s.Remove #1/19/2017#
    Debug.Print s.Exists(#1/19/2017#)
End Sub

Который печатает True, False и False, как и ожидалось.

person Blackhawk    schedule 19.01.2017