Мое почтение, джентльмены. Родилась идея завернуть один из стандартных контейнеров С++ (std::unordered_map) в СОМ (для возможности пользоваться и из VBA) Идея понравилась и реализована (часть методов будет добавлена при необходимости). Особенности: -ключ и значение сейчас реализованы как String теперь можно использовать любые данные в качестве ключа и значения -это хеш-таблица, а поэтому: поиск, вставка и удаление элементов имеют среднюю постоянную сложность. -стабильно быстрее в разы/порядки Collection и Dictionary -ВАЖНО! сейчас реализовано сохранения данных в контейнере до момента закрытия библиотеки (xll), т.е. выполнив процедуру в модуле, при выполнении следующей процедуру - данные в контейнере останутся. Это позволяет хранить данные (к примеру, как на листе). Если нужен чистый контейнер, просто очищаем когда нужно. Новая реализация позволяет создавать любое количество хеш-таблиц и автоматом удаляет их при завершении работы процедуры/функции
Test_Collection_vs_Dictionary_vs_UnorderedMap
Скрытый текст
Код
Sub Test_Collection_vs_Dictionary_vs_UnorderedMap() ' - что быстрее словарь или коллекция
Dim t, y, arr(1000000, 1) As String, i As Long, x As Long, xEnd As Long, FileTemp As String, value2
Dim coll As Collection: Set coll = New Collection
Dim Dict: Set Dict = CreateObject("Scripting.Dictionary")
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
Dim U: Set U = CreateObject("BedvitCOM.UnorderedMap")
'Dim U As New BedvitCOM.UnorderedMap ': Set r = New BedvitCOM.VBA 'раннее связывание
xEnd = 1000000 'количество итераций
For x = 1 To xEnd 'МАССИВ для проверки
arr(x, 0) = x
arr(x, 1) = "\\0.0.0.0\work$\05_КД\05-04_КС\КАТАЛОГИ\тест\0009613%01-С8-01.jpg" & x
Next
t = Timer
For x = 1 To xEnd 'добавляем элементы
coll.Add arr(x, 0), arr(x, 1)
Next
Debug.Print "Внесение данных, Collection.Add = " & Timer - t
t = Timer
For x = 1 To xEnd 'добавляем элементы
Dict.Add arr(x, 1), arr(x, 0)
Next
Debug.Print "Внесение данных, Dictionary.Add = " & Timer - t
t = Timer
For x = 1 To xEnd 'добавляем существующие элементы
y = U.TryEmplace(arr(x, 0), arr(x, 1))
Next
Debug.Print "Внесение данных, U.TryEmplace = " & Timer - t
Debug.Print "Итого элементов в UnorderedMap " & U.Size 'количество элементов контейнера
t = Timer
For x = 1 To xEnd 'ищем существующие элементы
y = coll.Item(arr(x, 1))
Next
Debug.Print "Поиск верных данных, Collection.Item = " & Timer - t
t = Timer
For x = 1 To xEnd 'ищем существующие элементы
y = Dict.Item(arr(x, 1))
Next
Debug.Print "Поиск верных данных, Dictionary.Item = " & Timer - t
t = Timer
For x = 1 To xEnd 'ищем существующие элементы
U.Find arr(x, 1), value2
Next
Debug.Print "Поиск данных, U.Find = " & Timer - t
End Sub
Результаты на 1 млн строк, с 1 млн итераций:
Внесение данных, Collection.Add = 13,09766 Внесение данных, Dictionary.Add = 73,87109 Внесение данных, U.TryEmplace = 0,9726563 Поиск верных данных, Collection.Item = 4,703125 Поиск верных данных, Dictionary.Item = 72,79688 Поиск данных, U.Find = 0,8007813 Итого элементов в UnorderedMap 1000000
Как использовать? -Так же как и словарь или коллекцию
Код
Sub UnorderedMap()
Dim U As New BedvitCOM.UnorderedMap ': Set r = New BedvitCOM.VBA 'раннее связывание
Dim key, value, value2, sizeU, x
key = "key"
value = "value"
If (U.Insert(key, value) = 0) Then MsgBox "Элемент уже существует и не был обновлен"
If (U.InsertOrAssign(key, value) = 0) Then MsgBox " = 0 - Элемент обновлен" 'если = 1 то создан новый
Debug.Print U.Size 'количество элементов контейнера
If (U.Find(key, value2) = 0) Then MsgBox "Не удалось найти элемент"
Debug.Print value2 'выводим найденный результат по ключу
If (U.Erase(key) = 0) Then MsgBox "Не удалось удалить элемент" 'очистка элемента по ключу
Debug.Print U.Size
U.Clear 'очистить весь контейнер
End Sub
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Сравнение на коротких и длинных ключах (где-какое в названии файла)
Коды (присутствует вывод прогресса в StatusBar)
Код
Option Explicit
Option Private Module
'====================================================================================================
Sub Test()
Tester
End Sub
'====================================================================================================
Private Sub Tester(Optional fLong As Boolean)
Dim BVdic As New BedvitCOM.UnorderedMap
Dim coll As New Collection
Dim dic As New Dictionary
Dim arrNum(), arrOut(), arrData()
Dim x, txName$, key$, vl$, t!, r&, c&, n&
Dim fColl As Boolean, fDic As Boolean
arrOut = Range("_out").Value2
arrNum = Range("_num").Value2
Application.StatusBar = "Prepare …"
ReDim arrData(1 To 1000000, 1 To 2)
For r = 1 To UBound(arrData, 1)
' arrData(r, 1) = r
' arrData(r, 1) = "_" & r
arrData(r, 1) = "test string #" & r
If fLong Then arrData(r, 1) = "_very long string only for this test becase NOBODY use such crazy KEYS in his project | " & arrData(r, 1)
arrData(r, 2) = "val " & r
Next r
fColl = True: fDic = True
rep:
If fColl Then txName = "Coll": c = 1: GoTo nx1
If fDic Then txName = "Dict": c = 2: GoTo nx1
txName = "BedVit": BVdic.Clear: c = 3
nx1:
For r = 1 To UBound(arrNum, 1)
Application.StatusBar = "«" & txName & "». Testing on " & Format$(arrNum(r, 1), "#,##0") & " elements": DoEvents
t = Timer '----------------------------------------------------------
For n = 1 To arrNum(r, 1)
key = arrData(n, 1)
vl = arrData(n, 2)
If fColl Then coll.Add vl, key: GoTo nx2
If fDic Then dic.Add key, vl: GoTo nx2
If BVdic.TryEmplace(key, vl) = 0 Then Debug.Print key, vl: Err.Raise xlErrNA
nx2: Next n
arrOut(r, c) = Format$(1000 * (Timer - t), "0") ' Add
t = Timer '----------------------------------------------------------
For n = 1 To arrNum(r, 1)
key = arrData(n, 1)
vl = arrData(n, 2)
If fColl Then
x = coll(key)
If x <> vl Then Debug.Print x, vl: Err.Raise xlErrNA
GoTo nx3
End If
If fDic Then
x = dic(key)
If x <> vl Then Debug.Print x, vl: Err.Raise xlErrNA
GoTo nx3
End If
If BVdic.Find(key, x) = 0 Then Err.Raise xlErrNA
If x <> vl Then Debug.Print x, vl: Err.Raise xlErrNA
nx3: Next n
arrOut(r, 3 + c) = Format$(1000 * (Timer - t), "0") ' Find
t = Timer '----------------------------------------------------------
If fColl Then Set coll = New Collection: GoTo nx4
If fDic Then dic.RemoveAll: GoTo nx4
BVdic.Clear
nx4: arrOut(r, 6 + c) = Format$(1000 * (Timer - t), "0") ' Clear
Next r
If fColl Then fColl = False: GoTo rep
If fDic Then fDic = False: GoTo rep
Application.StatusBar = False
Range("_out").Value2 = arrOut
MsgBox "DONE", vbInformation
End Sub
'====================================================================================================
Private Function CollExists(key$, coll As Collection) As Boolean
On Error Resume Next
coll.Item key
If Err = 0 Then CollExists = True
On Error GoTo 0
End Function
'====================================================================================================
Выводы
• разница по общему пробегу составляет от ~10 (13 на коротких ключах) до ~30 (28 на длинных ключах) раз — это, безусловно, быстрее на порядок • на объёмах до 10 тыс ключей нет разницы, использовать BedVit'овские словари или штатные (обязательно в раннем связывании, иначе они хуже работают) • на объёмах от 50 тыс ключей начинается КРАТНЫЙ выигрыш во времени у hash перед штатным словарём • на 500 тыс, наконец, включается коллекция и обгоняет штатный словарь, на 1 млн выигрыш уже ~ 3 раза, но BedVit к тому времени уже быстрее в ДЕСЯТКИ раз
Итоги
• я бы сказал, что для большинства задач при объёме до 50 тыс ключей нет смысла запускать хэш, но не стоит забывать, что это обычная (с виду) надстройка Excel и, если она подключена, то все скоростные инструменты всегда доступны, а значит можно просто использовать хэш ВСЕГДА, не задумываясь об объёмах • цитата о переосмыслении "давно известных фактов" в моей подписи снова доказала свою актуальность — я послушал мамкиных тестеров и подумал, что коллекции вообще не бывают нужны, если есть словари, однако для огромного количества (более 500 тыс) ключей, как выясняется, они очень даже пригодятся. Да это очень редко может быть нужно, но это нужно знать и это быстрее бинарного поиска (не тестил, но так должно быть), которым я ранее заменял словари на больших объёмах
Разумеется, если у вас стоит эта бесплатная (пока) надстройка, то вам вообще не стоит переживать об объёмах и выбирать инструменты в зависимости от них
Впереди ещё сравнение целочисленных ключей, хранение массивов в качестве элементов словаря (не факт) и повторный тест улучшенной версии (она уже в разработке) с блэкджеком и прочими атрибутами счастливой жизни успешного разработчика
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Внесены дополнения: +в качестве ключа можно использовать любые данные (не включая ссылки на массивы и объекты), ключ хранится как строка (конвертируется, если нужно, из другого типа данных), значение - как есть +в качестве значения можно использовать любые данные (включая ссылки на массивы и объекты и даже другую хеш-таблицу) +можно создавать любое количество хеш-таблиц +теперь их не нужно очищать, при выводе из функции они удаляются сами +можно как добавить новый элемент, так и перезаписать уже существующий (InsertOrAssign) +скорость выше в 2 раза в сравнении с 1м вариантом - за счет некоторых оптимизаций ключ - число Внесение данных, U.Insert = 0,6914063 Внесение данных, U.InsertOrAssign = 1,503906 Поиск данных, U.Find = 0,4492188 ключ - текст Внесение данных, U.Insert = 0,609375 Внесение данных, U.InsertOrAssign = 1,28125 Поиск данных, U.Find = 0,3320313
+Добавил загрузку из массива/диапазона (ключ-значение) +Добавил выгрузку в массив/диапазон (ключ-значение)
Код
Dim U As New BedvitCOM.UnorderedMap, arrU
x = U.RangeSet(Range("a1:b5").value) 'x - количество загруженных элементов в map
x = U.RangeGet(arrU, 1) 'x - количество выгруженных элементов в массив '1-нижняя граница массива
Range("c1").Resize(x, 2) = arrU
'краткая запись
U.RangeSet Range("a1:b5").value
U.RangeGet arrU
Range("c1").Resize(x, 2) = arrU
bedvit, круто! Спасибо! Если в диапазоне для ключей были дубли, это ведь не проблема? Просто пропустит повторы, отобрав ПЕРВЫЕ ключи с их значениями (или ПОСЛЕДНИЕ, перезаписывая) Можно ли передать двумерный массив из одного столбца (только ключи)? Одномерный массив (только ключи)? 2 одномерных массива?
Если нет, то нужен быстрый конвертор (да и в принципе он нужен): собрать двухмерный массив из 2ух одномерных и получить одномерный массив из столбца двухмерного Если будет загрузка только ключей из диапазона, то возникнет необходимость в обработке нескольких областей (видимые ячейки после фильтра) Можно даже и со значениями реализовать (проверять каждую область, чтобы было ровно 2 столбца)
Всей этой универсальности можно добиться без раздувания конкретного инструмента (словаря), а реализовав мою идею по получению информации о диапазоне в массив + вышеописанные универсальные функции резки/объединения массивов. Это было бы круто и много где применимо
P.S.: про развитие Ultra ReDim Preserve я тоже записал - пока очень хорошо, но мало возможностей + Рекомендую вынести в отдельную тему, а ту оставить для поисковика
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Если были дубли, попадает первый, остальные игнорируются (хм, получилось незапланированная функция удаления дубликатов по ключам ) Загружается и выгружается всегда двухмерный массив (ключ-значение)
я бы добавил функционала - нужные штуки. Не знаю, насколько сложно, правда, ноя бы топил за универсальные функции (добавил выше), чтобы можно было быстро и легко "привести" к исходному виду
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Провел тест: 1 млн значений (текст - 10 символов): порядка 650 000 уникальных Штатная RemoveDuplicates - 22 сек RangeSet+RangeGet - 1,15 сек (4 сек с выгрузкой на лист) (подробнее ниже)
Код
Sub Test_RemoveDuplicatesVBA()
Dim U As New BedvitCOM.UnorderedMap ': Set r = New BedvitCOM.VBA 'раннее связывание
Dim x, arrU, t, t1, t2, t3, t4
test_array_string
t1 = Timer
x = U.RangeSet(Range("a1:b1000000").value) 'x - количество загруженных элементов в map
t2 = Timer
x = U.RangeGet(arrU, 1) 'x - количество выгруженных элементов в массив '1-нижняя граница массива
t3 = Timer
Range("c1:d5").Resize(x, 2) = arrU
t4 = Timer
Debug.Print "RangeSet = " & t2 - t1
Debug.Print "RangeGet = " & t3 - t2
Debug.Print "RangeSet+RangeGet = " & t3 - t1
Debug.Print "RangeSet+RangeGet+Out = " & t4 - t1
t = Timer
ActiveSheet.Range("$A$1:$A$1000000").RemoveDuplicates Columns:=1, Header:=xlNo
Debug.Print "RemoveDuplicates = " & Timer - t
End Sub
Sub test_array_string()
Dim i&, j&, t#, a(), xEnd, yEnd
ActiveSheet.Cells.Clear
xEnd = 1000000 'задаем верхние границы диапазона строк
yEnd = 1 'задаем верхние границы диапазона столбцов
ReDim a(1 To xEnd, 1 To yEnd)
t = Timer
For i = 1 To xEnd: For j = 1 To yEnd
a(i, j) = Int(Rnd * xEnd) & "Test"
Next: Next
[A1].Resize(xEnd, yEnd) = a: Debug.Print Timer - t
End Sub
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Jack Famous написал: Я правильно понимаю, что вместо Range("a1:b1000000").value я могу сформировать в памяти и передать свой двумерный массив, минуя лист?
Да, только массив должен быть типа Variant. +ВАЖНО: данные не всегда выводятся в том порядке, в котором были изначально.
отлично — даже добавлять ничего не придётся, т.к. у меня и так в 99% процедур имеется вариативная переменная
Цитата
bedvit: данные не всегда выводятся в том порядке, в котором были изначально
воу - это важное замечание. А при заполнении в цикле, я получу массив тоже не в том порядке?
Подумай над моими предложениями выше, пожалуйста - особенно про получение информации о диапазоне в массив, т.к. при работе с большим количеством областей, даже массив значений долго получать, не говоря уже про форматы, адреса и т.д. …
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Jack Famous написал: я получу массив тоже не в том порядке?
да, порядок не определен. Для каждого ключа создается хеш, далее по хеш - данные хранятся в UnorderedMap. Как они там хранятся - это внутренняя реализация этого контейнера. Если порядок критичен (в каких случаях?), можно в значения записать первоначальный индекс, и по нему просто отсортировать полученный конечный массив. Обычно данные хранятся не отсортированные и их все равно надо сортировать, тогда это не важный фактор, если не надо сортировать, то тогда тоже не важный. В любом случае первоначальных позиций уже не будет. Т.е. сохранения порядка выходит не так значимо?
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Так можно получить одномерный массив ключей в том порядке, в котором они попадали в карту/словарь
Код
ReDim arrNew(1 To UBound(arrOld,1))
For r=1 To UBound(arrOld,1)
If map.Insert(arrOld(r,1),0) Then rr=rr+1: arrNew(rr)=arrOld(r,1)
Next r
If rr<> UBound(arrNew) Then ReDim Preserve arrNew(rr)
Прочие тонкости
• FindбыстрееInsert, а InsertбыстрееInsertOrAssign — немного, но стабильно. При этом Find + Insert всегда заметно дольше простого InsertOrAssign • если при поиске Find не нужно возвращать значение по ключу, то не передавайте параметр (появилась возможность писать If map.Find(key) - будет быстрее • если значения не нужны, то вместо них (параметр обязательный) ставьте цифру 0 - это по скорости как True/False/Empty, но короче, а добавлять строку нолевой длины "" немного дольше • при сборе карты/словаря Find нужен только, если нужно производить какие-то действия со значениями (счётесли, суммесли, сцепитьесли). Порядок добавления значений (первое попавшееся или последнее) контролируется использованием методов Insert или InsertOrAssign — соответственно
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
1. Это код из сообщения #1. У меня почему-то ошибка 438 Object doesn't support this poroperty or method на строке ниже. См. картинку с ошибкой под сообщением.
Код
y = U.TryEmplace(arr(x, 0), arr(x, 1))
Этот метод TryEmplace поддерживается в библиотеке (v.2.0.2.2) ? или это какой-то старый метод, которого уже в библиотеке нет, но коды в этой теме на него есть? Если этого метода у карт нет, то нужно заменить код в сообщении #1 на рабочий, с правильным методом
2. Чтобы взять данные из столбца А в карту обязательно в адресе ещё и столбец В брать, как у вас в примере?
Код
x = U.RangeSet(Range("a1:b1000000").Value)
В тесте мы просто берём данные из столбца А, а в коде прописано 2 столбца А и В. Если заменить В на А вот так Range("a1:a1000000").Value, то Excel аварийно закрывается или это подразумевается, что в столбце А ключи, а в В значения?
3. если мы выгружаем 600.000 значений в столбец С, то зачем указывать диапазон С1:D5 ? Это строка из сообщения #9
Код
Range("c1:d5").Resize(x, 2) = arrU
4. Вопрос по коду из сообщения #1. Вот в этой строке ошибка
Код
For x = 1 To xEnd 'ищем существующие элементы
U.Find arr(x, 1), value2
Next
значение value2 - всегда пустое, надо другой элемент массива подставлять
Код
For x = 1 To xEnd 'ищем существующие элементы
U.Find arr(x, 0), value2
Next
5. Зачем в Сообщении #1 строка. В коде, вроде, сравниваются скорости Коллекции, Словаря и карты, а тут фигак и FSO
Код
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
New, 1. да, устарела, вот полное описание с кодом. 2. да, загружаются ключ-значения (могут быть пустыми, но массив все равно из 2 столбцов). Надо будет прописать проверку, что бы без вылетов.э, в случае если неправильный массив подают. 3. Потому что выгружается ключ и значение это два столбца 4. А вы загружали значения к ключу? 5.Да, артефакт предыдущих эпох. Поправлю пример в 1м сообщении.
New, здравствуйте и добро пожаловать Наконец, хоть кто-то ещё вник — мне было одиноко
2. Вылет это, конечно, жёстко Я этим методом не пользуюсь, потому что нечасто вот так получается что есть на листе 2 столбца рядом с ключами в левом и значениями в правом. Очень узко, как по мне, а по скорости также вроде. Коротко - факт, но редко, когда получается это использовать
4. Загружать ЗНАЧЕНИЕ нужно ВСЕГДА (дополнительные проверки съедали бы время) и проще писать в качестве них 0, если нужны только ключи — я в #16 писал это под спойлером "Прочие тонкости"
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
bedvit, Jack Famous, спасибо, стало понятнее. да, описание на CyberForum я читал. Да, там про TryEmplace ничего нет. Нужно поправить код в Сообщении #1 - заменить TryEmplace на Insert По поводу вопроса
Цитата
bedvit написал: 4. А вы загружали значения к ключу?
Это не мой код, это код из Сообщения #1 (под спойлером) я взял его и тестировал, там в массиве "x" создаётся 1 млн записей и они сперва загружаются в Коллекцию, потом в Словарь, потом в MAP, потом делается поиск по Коллекции, потом по Словарю, потом по MAP и показывается затраченное время. Просто если прогонять по F8, то значение Value2 - не получает значение при методе Find, а если изменить индекс массива x, то значения находятся.
Код
For x = 1 To xEnd 'ищем существующие элементы
U.Find arr(x, 1), value2 '<- вот тут надо подставлять ключ, который записан в arr(x, 0), а не в arr(x, 1)
Next
Скорость работы, конечно, впечатляет... интересно, а почему Microsoft так не сделало ?
потому что мелкомягким ещё очень далеко до нашего Виталия Он использует другой контейнер, который изначально быстрее (но, например, не сохраняет порядок, поэтому и unordered - решение я показал под спойлером ранее) плюс, что самое важное, "пропиливает" (как он сам называет) каждый код под максимальную производительность. Это очень сложно, но, как видите, того стоит
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Нещ написал: Скорость работы, конечно, впечатляет... интересно, а почему Мицрософт так не сделало ?
Почему не сделало что? Не развивает скоростные контейнеры для VBA? А зачем? Для С++ вот сколько контейнеров в стандартной библиотеке. Под любую задачу. Думаю Майкрософт здесь поучаствовали тоже. Я выбрал std::unordered_map по причине того, что это достаточно быстрая универсальная хеш-таблица с такой особенностью: Поиск, вставка и удаление элементов имеют среднюю постоянную сложность. Т.е. не зависят от количества элементов в контейнере. В теории можно было выбрать любой другой контейнер, с другим набором свойств.
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
bedvit написал: 2. да, загружаются ключ-значения (могут быть пустыми, но массив все равно из 2 столбцов). Надо будет прописать проверку, что бы без вылетов.э, в случае если неправильный массив подают.
Начиная с версии XLL 2.0.2.4 и СОМ 1.0.5.3 - такая проверка реализована.
bedvit, отлично и спасибо! Теперь надо сделать функцию чтобы можно было быстро "распилить" 2мерный массив на 2 одномерных с заданной нижней границей (или получить любой "столбец"/"строку" в отдельный массив) Потом ещё одну - для сбора 2мерного из одномерных Потом вспомнить, что собирался ReDim Preserve нормальный сделать вместо штатного
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
у меня два одномерных массива, один маасив строк - 50 элементов, другой числа 10 элементов. Что в итоге должнн получить, какой тип массива, какой размерности?
зачем это? Просто обращайся к нужному столбцу в массиве и ОК. Если прям нужен одномерный массив можно использовать Array2Dto1D() и найти нужный "бывший столбец" как количество элементов столбца умноженное на порядковый номер нужного столбца. Если столбец был первым, то это первый элемент массива (надеюсь понятно написал).
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄