Уникальные значения из 1D-массива, без итерации - PullRequest
4 голосов
/ 10 января 2020

рискуя быть топи c, я решил поделиться некоторым кодом, Q & A-style . Если общее мнение таково, что это будет не так c Я буду рад удалить, если это будет необходимо.


Фон

Можем ли мы получить все уникальные значения из любого 1D-массива или Range объекта, превращенного в 1D-массив, без необходимости перебирать его элементы? Насколько я понимаю, общий консенсус заключается в том, что нужно перебирать различные элементы, где лучший способ сделать это - это словарь или коллекция для хранения уникальных значений. Здесь - это то, что Я нашел работы очень хорошо для этой цели.


Вопрос

Так как же можно go о извлечении уникальных элементов из 1D-массива, например:

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")

Где результирующий массив будет:

{"A", "C", "D", "E", "G"}

Ответы [ 3 ]

5 голосов
/ 10 января 2020

С новыми функциями массива Dynami c его можно упростить до:

Sub test()

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
With Application
    Dim uniques as variant
    uniques = .Transpose(.Unique(.Transpose(arr)))
End With

End Sub

enter image description here

Для новой формулы Uniques требуется вертикальный массив и это может быть 2d. Он действует как Range.RemoveDuplicate без возможности выбора столбцов.

4 голосов
/ 10 января 2020

На самом деле весь необходимый код - всего несколько строк:

Sub test()

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
With Application
    uniques = .Index(arr, 1, Filter(.IfError(.Match(.Transpose(.Evaluate("ROW(1:" & UBound(.Match(arr, arr, 0)) & ")")), .Match(arr, arr, 0), 0), "|"), "|", False))
End With

End Sub

Вышеприведенный код вернет 1D-массив, возвращающий все уникальные элементы в нашем исходном массиве:

enter image description here


Объяснение:

Строка, которая извлекает все эти значения, выглядит интенсивно, Давайте разберем его на части:


enter image description here

Application.Match имеет возможность работать с массивами в своих параметрах. Итак, в основном мы смотрим на: .Match({"A","A","C","D","A","E","G"},{"A","A","C","D","A","E","G"},0). Тогда возвращаемый массив будет иметь вид: {1,1,3,4,1,6,7}, и на самом деле это первые позиции, в которых находится каждое значение. Этот результат будет основой для дальнейшего развития.


enter image description here

Мы можем видеть третий .Match в нашем коде, который мы нужно в основном сказать следующее: .Match({1,2,3,4,5,6,7},{1,1,3,4,1,6,7},0). Первый параметр - это то, что получается с помощью выделенного выше кода.

Где .Evaluate("ROW(1:" & UBound(.Match(arr, arr, 0)) & ")") вернет массив значений из 1-7, Application.Transpose вернет его так, что это 1D-массив.


enter image description here

На последнем шаге будет возвращен массив, содержащий ошибки, однако код не сломается, поскольку мы используем Application вместо WorksheetFunction. Полученный массив будет выглядеть как {1,Error 2042,3,4,Error 2042,6,7}. Теперь весь смысл состоит в том, чтобы избавиться от Error значений.

Способ сделать это - через Application.IfError, который оценит массив и изменит все значения ошибок на значение данной строки. В нашем случае я использовал символ трубы. Пользователь должен выбрать символ, достаточно уникальный, чтобы он не отображался ни в одном из элементов исходного массива. Итак, после оценки. Наш текущий массив будет выглядеть так: {1,|,3,4,|,6,7}.


enter image description here

Теперь мы получили массив с символами канала, которые мы бы хотели, чтобы они вышли! Быстрый способ сделать это с помощью функции Filter. Filter возвращает массив с элементами или без них, которые соответствуют нашим критериям (в зависимости от TRUE или FALSE в его третьем параметре).

Итак, в основном мы хотим вернуть массив следующим образом: Filter(<array>, "|", False). Результирующий 1D-массив теперь выглядит следующим образом: {1,3,4,6,7}.


enter image description here

В некоторой степени он у нас уже есть. Нам просто нужно вырезать правильные значения из нашего исходного массива. Для этого мы можем использовать Application.Index. Мы просто хотим указать .Index, какие строки нам интересны. Для этого мы можем загрузить наш ранее найденный 1D-массив. Таким образом, код будет выглядеть так: .Index(arr1, <array>, 1), что приведет к созданию 1D-массива: {"A","C","D","E","G"}


Вывод:

Вот так. Одна строка (с более чем одной операцией) для извлечения 1D-массива уникальных значений из другого 1D-массива без итерации . Этот код готов к использованию в любом 1D-массиве, объявленном с arr.

Полезно? Я не уверен на 100%, но я наконец достиг того, что я пытался в своем проекте. Полученный массив можно сразу использовать в любой задаче, в которой нужно использовать уникальные значения.

Сравнение: словарь и приложение. Методы:

При сравнении случайных предметов в Range(A1:A50000) производительность действительно получает удар. Таким образом, сравнение времени между итеративным словарем и не итеративным подходом Application.Methods выполняется с шагом 1000 пунктов. Ниже результата отметки 1000 предметов и каждых 10000 предметов (в секундах):

| Items     | Dictionary    | Methods       |
|-------    |------------   |-------------  |
| 1000      | 0,02          | 0,03          |
| 10000     | 0             | 0,88          |
| 20000     | 0,02          | 3,31          |
| 30000     | 0,02          | 7,3           |
| 40000     | 0,02          | 12,84         |
| 50000     | 0,03          | 20,2          |

Используемый подход Dictionary:

Sub Test()

Dim arr As Variant: arr = Application.Transpose(Range("A1:A50000"))
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")

Dim x As Long

For x = LBound(arr) To UBound(arr)
    dict(arr(x)) = 1
Next x

Dim uniques As Variant: uniques = dict.Keys

End Sub

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

Я уверен, что время обработки будет более ограничено с новыми функциями массива Dynami c, когда показано @ ScottCraner.

1 голос
/ 26 января 2020

Подход с помощью FilterXML()

Просто для того, чтобы обогатить разнообразные тонкие решения, описанные выше для пользователей Excel 2016+ / Office 365, я демонстрирую подход с помощью новой функции рабочего листа FilterXML():

Ссылки по теме

Справка MS

Отображение неравных значений в массив Excel

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