Jack Famous, приветствую! Тема старая, но я также задавалась вопросом по замене или оптимизации Join, т.к. есть особенности и ограничения у данного метода! От себя добавлю, и Вам тоже думаю будет интересно:
Цитата |
---|
написал: Есть еще одна интересная тема - байтовое представление текста. Эта штука работает очень быстро. |
Согласна быстро, но не достаточно и идея неплохая, но данный способ не учитывает само время преобразования из строки в байты! А вышеперечисленные алгоритмы напрямую работают со строками и лишены этой операции! А чем длиннее строки, тем дольше будет осуществляется это преобразовании строки в байт-массив! Как итог: не всегда работа с байтами быстрее работы с данными на прямую, это касается высокоуровневый кодов!
Мы имеем 3 алгоритма:
Код |
---|
Sub ConcatFast()
Dim temp(), x, tm!, i&, j&, v$, line$
If Not GetArray(temp) Then Exit Sub Else tm = Timer: line = " "
For Each x In temp
v = x & ";": j = i + Len(v)
If j > Len(line) Then line = line & Space$(Len(line))
Mid$(line, i + 1) = v: i = j
Next x
line = Left$(line, j - 1)
Debug.Print "ConcatFast (Time): " & Format$(Timer - tm, "0.000 ms")
Debug.Print "ConcatFast (Len): " & Len(line)
End Sub
|
Код |
---|
Sub ConcatJoin()
Dim arrNum(), temp(), x, tm!, n&, line$
If Not GetArray(temp) Then Exit Sub Else tm = Timer: ReDim arrNum(0 To (UBound(temp, 1) * UBound(temp, 2) - 1)): n = -1
For Each x In temp
n = n + 1: arrNum(n) = x
Next x
line = Join(arrNum, ";")
Debug.Print "ConcatJoin (Time): " & Format$(Timer - tm, "0.000 ms")
Debug.Print "ConcatJoin (Len): " & Len(line)
End Sub
|
Код |
---|
Function aJoin(arr(), del)
Dim a&, b&
For a = LBound(arr) To UBound(arr): b = b + Len(arr(a)): Next
aJoin = Space$(b + (UBound(arr) - LBound(arr)) * Len(del)): b = 1
For a = LBound(arr) To UBound(arr)
Mid$(aJoin, b, Len(arr(a))) = arr(a): b = b + Len(arr(a))
If a <> UBound(arr) Then Mid$(aJoin, b, Len(del)) = del: b = b + Len(del)
Next a
End Function
|
Комментарий по
ConcatFast: Всё хорошо и спасибо за данное сконструированное решение, но есть один недочёт, а именно:
Код |
---|
If j > Len(line) Then line = line & Space$(Len(line)) |
У вас в тестах Вы формируете массив из единиц:
Код |
---|
Function GetArray(arr()) As Boolean
Dim c&, r&: ReDim arr(1 To 1000000, 1 To 100)
For c = 1 To UBound(arr, 2)
For r = 1 To UBound(arr, 1)
arr(r, c) = 1 ' Я про это!
Next r
Next c
GetArray = True
End Function
|
К сожалению, в большинстве случаев таких данных практически не бывает (Len = 1), поэтому, если Вы увеличите длину тестируемых данных хотя-бы на 1 (Len = 2), то Вы получите некорректные данные на начальном этапе, на выводе:
Код |
---|
Function GetArray(arr()) As Boolean
Dim c&, r&: ReDim arr(1 To 1000000, 1 To 100)
For c = 1 To UBound(arr, 2)
For r = 1 To UBound(arr, 1)
arr(r, c) = 1 & "T"
Next r
Next c
GetArray = True
End Function |
Вывод:
Код |
---|
"1T 1 1T 1T;1T;1T;1T;1T;1T;1T...
|
Это потому, что начальная длина пустой строки, которая впоследствии наращивается имеет начальную длину в один пробел:
И при первой же итерации Вам нужно вставить 3 символа ("1T" + разделитель ";"), но строка формируется только на 2 символа:
Код |
---|
line = line & Space$(Len(line)) ' line = " "
|
И уже при первой итерации, взамен "1T;" мы получаем "1T". Далее данные постепенно нормализуются, но если вставить данные с длиной > 2, например 3 (1TS),то Вы словите ошибку, т.к. метод Mid$ будет вынужден обращаться к несуществующему индексу, ведь длина наращиваемой строки будет меньше, чем требуется!
В общем это духота и не каждый поймет, что я имела ввиду, т.к. это специфичная тема и сложно объяснить простым языком (ещё бы уметь это делать) поэтому предлагаю решения по исправлению данного бага и забыть об этом недочёте)
Чтобы исправить данный баг, предлагаю два решения:
- На начальном этапе взять длину строки с запасом, чтобы длина line = " " была гарантированно больше первого элемента просматриваемого массива, далее данные нормализуются. Например у Вас первый элемент имеет длину в 12 символов (x(0) = "Hello_Nig..."), значит line = Space(Len(x) * 2). Но для этого необходимо прописывать дополнительный алгоритм на определение длины первого элемента... Поэтому 2 оптимальный, на мой взгляд, вариант;
- Внести изменение в строчку и добавить следующее:
Код |
---|
До: If j > Len(line) Then line = line & Space$(Len(line))
После: If j > Len(line) Then line = line & Space$(Len(line) + Len(x))
|
Всё очень просто и таким образом Вы не особо теряете в производительности (где-то данное решение будет показывать даже лучший результат, всё зависит от данных), и гарантированно получаете корректные данные на выходе, так как наращивание происходит всегда с запасом!
Далее
ConcatJoin. Сам Join стандартными методами VBA обогнать не удастся! Microsoft постарались и сделали одну из шустрых функций на VBA:
Цитата |
---|
написал: line = Join(arrNum, ";") |
Поэтому решающее значение будут иметь входные данные и скорость их преобразования в одномерный массив:
Цитата |
---|
написал: For Each x In temp n = n + 1: arrNum(n) = x Next x |
Как итог:
ConcatJoin самый оптимальный и простой вариант, т.к. в большинстве случаев Вы не будете работать с Big Data через Excel, посредством VBA!
И
aJoin. Что-то мне подсказывает, что
Anchoret, шарит и увлекался компилируемыми языками, ведь данное решение,
на мой взгляд, отлично себя покажет именно в компилируемом варианте (вычислить общую длину получаемой строки, единожды выделить память под данную строку и заполнить её через цикл, без лишнего изменения и обращения к менеджеру памяти OS, с просьбой выдать ещё свободного места😊).
Но хочу указать на то, что это только моё предположение и я могу ошибаться!
Что по данному варианту на VBA, то мы имеем современный интерпретируемый вариант реализации языка (компиляция в промежуточное представление (подобие байт-кода) и выполнение на "виртуальной машине" Excel) и в данном случае это решение будет показывать себя с лучшей стороны, только при очень больших массивах данных, т.к. мы тратим наше "драгоценное" время на подсчёт общей длины строки и используем метод
Mid$ два раза, взамен одного в методе
ConcatFast. Но в методе
ConcatFast мы имеем конкатенацию
"line & Space$(Len(line))" и чем больше входных данных, тем медленнее будет отрабатывать данный алгоритм, а
aJoin наоборот будет улучшать результат по отношению к
ConcatFast!
Итог: Берите
ConcatJoin и не парьтесь)
Цитата |
---|
Укажу на то, что это старая статья (2019 год) и никаких претензий я не Высказываю (на момент публикации я вообще не имела никаких дел с программированием😁)! Только поделилась своими мыслями и указала на недочёт в алгоритме! Буду признательна аргументированной критике в мой адрес, если это имеет место быть! |