Итак, чтобы удалить строки из Excel, есть несколько способов, которые мне известны:
Грубое удаление через Rows().Delete! (Подходит для небольших таблиц с данными);
Сортировка массива + удаление одной операцией (Быстрее, но всё же медленно на больших объёмах, т.к. сортировка это также затратная операция);
Через удаление дубликатов (Реализован ниже);
Скорее всего есть ещё множество вариантов, но если кто шарит, тот предложит!
Теперь сам код и файл для теста:
Скрытый текст
Детект (обнаружение строк):
Код
Sub Детект_строк()
' Объявление переменных
Dim Arr_In(), Arr_Crit(), LastRow&, strFromArr$, Count_DelRow&, Criterion$, Pos&, I&, n&, tmp&
' Копируем данные из Листа1 в массив
LastRow = Лист1.Cells(Rows.Count, 1).End(xlUp).Row
Arr_In = Лист1.Range("A2:A" & LastRow).Value
' Копируем данные из Листа2 в массив
LastRow = Лист2.Cells(Rows.Count, 1).End(xlUp).Row
Arr_Crit = Лист2.Range("A2:A" & LastRow).Value
' Приводим текст в нижний регистр (LCase)
For I = LBound(Arr_In, 1) To UBound(Arr_In, 1)
Arr_In(I, 1) = LCase$(Arr_In(I, 1))
Next I
' Приводим текст в нижний регистр (LCase)
For I& = LBound(Arr_Crit, 1) To UBound(Arr_Crit, 1)
Arr_Crit(I, 1) = LCase$(Arr_Crit(I, 1))
Next I
' Дополнительный массив для формирования шаблона и последующего преобразования в строку
Dim Arr_Add(): ReDim Arr_Add(LBound(Arr_In, 1) To UBound(Arr_In, 1))
' Заполнение массива нужными данными c индексацией. Например:
' {1} - vbNullChar 1 vbNullChar https://msk.sweetmarin.ru/
' {2} - vbNullChar 2 vbNullChar https://teabakery.ru/
' {3} - vbNullChar 3 vbNullChar https://tortik-annuchka.com/
' ...
For I = LBound(Arr_In, 1) To UBound(Arr_In, 1)
Arr_Add(I) = vbNullChar & I & vbNullChar & Arr_In(I, 1)
Next I
' Формирование строки для последующего поиска подстрок (нужных совпадений)
strFromArr = Join(Arr_Add, vbNullChar)
n = 1: Count_DelRow = 0
Dim Arr_Boolean(): ReDim Arr_Boolean(LBound(Arr_In, 1) To UBound(Arr_In, 1), 1 To 1)
' Инициализация массива (она требуется в данном случае!)
For I = LBound(Arr_Boolean, 1) To UBound(Arr_Boolean, 1)
Arr_Boolean(I, 1) = False
Next I
' Перебираем массив с данными, которые нужно найти
For I = LBound(Arr_Crit, 1) To UBound(Arr_Crit, 1)
Criterion = Arr_Crit(I, 1): Pos = 1
' Находим все вхождения строки и вытаскиваем индекс
Do
' Если Pos > 0, значит Criterion есть в массиве
Pos = InStr(Pos, strFromArr, Criterion)
If Pos = 0 Then Exit Do
' Определяем первый Null (vbNullChar 1 "vbNullChar")*
tmp = InStrRev(strFromArr, vbNullChar, Pos) - 1
' Определяем второй Null ("vbNullChar" 1 vbNullChar)* и выдергиваем индекс
n = Val(Mid$(strFromArr, InStrRev(strFromArr, vbNullChar, tmp) + 1, 7))
' P.S. Выдергивается что-то типа "1 https".
' Функция Val позволяет нам отбросит весь текст и оставить только 1!
' Константа "7" взята, т.к. максимальная строка в Excel равна 7 символам (1048576)
' Инкремент (+1) переменной Pos и замена данных в массиве Arr_Boolean по индексу {N}
Pos = Pos + 1: Arr_Boolean(n, 1) = True
' Подсчет количества строк для удаления
Count_DelRow = Count_DelRow + 1
'* в кавычках помеченный Null, который определяем!
Loop
Next I
' Вдруг удалять будет нечего)
If Count_DelRow = 0 Then Exit Sub
' Отключаем обновление экрана
Application.ScreenUpdating = False
' Переходим на нужный лист
Лист1.Activate
' Вставка обработанных данных с пометкой строк для удаления
Лист1.Range("B2").Resize(UBound(Arr_Boolean, 1), 1).Value = Arr_Boolean
' Включаем обновление экрана
Application.ScreenUpdating = True
End Sub
Удаление строк:
Код
Sub Удалить_строки()
' Объявление переменных
Dim Arr_In(), LastRow&, I&, Inx&
' Копируем данные из Листа1 в массив
LastRow = Лист1.Cells(Rows.Count, 2).End(xlUp).Row
Arr_In = Лист1.Range("B2:B" & LastRow).Value
' Дополнительный массив для отсеивания ненужных строк
Dim Arr_Tmp(): ReDim Arr_Tmp(LBound(Arr_In, 1) To UBound(Arr_In, 1), 1 To 1)
' Перебираем весь массив флагов и выявляем ненужные строки
For I = 1 To UBound(Arr_In, 1)
If Arr_In(I, 1) Then ' True
' Фиксируем первую ненужную строку
If Inx = Empty Then Inx = I + 1
' Значение Истина, которое впоследствии и будет использовано в качестве критерия для удаления
Arr_Tmp(I, 1) = True
Else ' False
' Добавляем значение счетчика в массив как уникальный ID
Arr_Tmp(I, 1) = I
End If
Next
' Доп. проверка
If Inx = 0 Then Exit Sub
' Отключаем обновление экрана
Application.ScreenUpdating = False
' Вставка на лист
Range("B2").Resize(UBound(Arr_Tmp, 1), 1).Value = Arr_Tmp
' Удаление строк со значением ИСТИНА во 2 столбце, в строгом диапазоне
Range("A1:H" & UBound(Arr_Tmp, 1) + 1).RemoveDuplicates Columns:=2
' Удаление оставшийся ненужной строки со значением ИСТИНА
Rows(Inx).Delete Shift:=xlUp: ActiveSheet.UsedRange
' Очистка комментариев по детекту строк
Range("B2:B" & LastRow).ClearContents
' Включаем обновление экрана
Application.ScreenUpdating = True
End Sub
Точные данные по времени выполнения не замеряла, но всё выполняется на уровне кликов (120000 строк) (не более 1 сек.)
Jack Famous, здравствуйте. Да просто много Анастасий на форуме, решила сократить для удобства и мне так лучше)
Цитата
написал: как-то очень много (и не сказать, чтобы очень быстро) для простой простановки критериев для удаления…
Было слово "быстро", но не было "очень быстро"😁! Код хорошо себя будет показывать при большом количестве критериев для удаления. Опять же, в данном маленьком примере, который привёл realmen80, полный перебор будет немного быстрее себя показывать, т.к. данных немного! Но Вы можете предложить более быструю альтернативу, если имеется!
Цитата
написал: И самого удаления (как самой затратной операции по времени) нет.
Возможно realmen80, уже не нужен данный код, поэтому не стала заморачиваться) Да и способов быстрого удаления не так уж и много! Также если выполнять алгоритм удаления строк в большой таблице на слабом или даже на средничковом компе, то никакой алгоритм быстро не вывезет!
Цитата
написал: Удаляем лишние пробелы
Согласна, духота по делу, но всё же, как работает Trim уж думаю рассказывать никому не нужно! Trim$(" при ме р ") = "при ме р"
Так что, по удалению строк я на обеде слеплю алгоритмы, чтобы Вы могли оценить и прочее, да и к теме это вроде как относиться) А по детекту (обнаружению) строк можете предложить альтернативу (кроме Brute Force конечно, ведь мы здесь собрались, чтобы креативчик выдать!!!)
БМВ, здравствуйте. Опять же всё зависит от входных данных. Например поиск в массиве на 100 элементов в некоторых источниках рекомендуют производить полным перебором (без связки Сортировка + Бинарный поиск), т.к. вычислительной мощности в современном мире хватает для решения данной задачи. В моём примере я сделала код с "запасом", т.к. мне неизвестно количество критериев для удаления и количество совпадений, только количество строк (100 000), но конечно, полный перебор никто не отменял и всегда есть и будут моменты, где он будет предпочтительней усложнённых алгоритмов. Просто таких моментов меньше!
Sanja, добрый день. Это называется "Валидация входных данных" и цель была предоставить более-менее понятный код, поэтому я его закомментировала! Плюс по производительности разницы не будет, если Вы об этом!
realmen80, алгоритм на VBA для быстрого детекта (определения) нужных строк для удаления (для удобства всё закомментировала):
Скрытый текст
Код
Sub Пример()
' Обьявление переменных
Dim Arr_In(), Arr_Crit(), LastRow&, strFromArr$, Count_DelRow&, Criterion$, Pos&, I&, N&, tmp&
' Копируем данные из Листа1 в массив
LastRow = Лист1.Cells(Rows.Count, 1).End(xlUp).Row
Arr_In = Лист1.Range("A2:A" & LastRow).Value
' Копируем данные из Листа2 в массив
LastRow = Лист2.Cells(Rows.Count, 1).End(xlUp).Row
Arr_Crit = Лист2.Range("A2:A" & LastRow).Value
' Удаляем лишние пробелы (Trim) и приводим текст в нижний регистр (LCase)
For I = LBound(Arr_In, 1) To UBound(Arr_In, 1)
Arr_In(I, 1) = LCase$(Trim$(Arr_In(I, 1)))
Next I
' Удаляем лишние пробелы (Trim) и приводим текст в нижний регистр (LCase)
For I& = LBound(Arr_Crit, 1) To UBound(Arr_Crit, 1)
Arr_Crit(I, 1) = LCase$(Trim$(Arr_Crit(I, 1)))
Next I
' Дополнительный массив для формирования шаблона и последующего преобразования в строку
Dim Arr_Add(): ReDim Arr_Add(LBound(Arr_In, 1) To UBound(Arr_In, 1))
' Заполнение массива нужными данными c индексацией. Например:
' {1} - vbNullChar 1 vbNullChar https://msk.sweetmarin.ru/
' {2} - vbNullChar 2 vbNullChar https://teabakery.ru/
' {3} - vbNullChar 3 vbNullChar https://tortik-annuchka.com/
' ...
For I = LBound(Arr_In, 1) To UBound(Arr_In, 1)
Arr_Add(I) = vbNullChar & I & vbNullChar & Arr_In(I, 1)
Next I
' Формирование строки для последующего поиска подстрок (нужных совпадений)
strFromArr = Join(Arr_Add, vbNullChar)
N = 1: Count_DelRow = 0
Dim Arr_Boolean(): ReDim Arr_Boolean(LBound(Arr_In, 1) To UBound(Arr_In, 1), 1 To 1)
' Инициализация массива (она требуется в данном случае!)
For I = LBound(Arr_Boolean, 1) To UBound(Arr_Boolean, 1)
Arr_Boolean(I, 1) = False
Next I
' Перебираем массив с данными, которые нужно найти
For I = LBound(Arr_Crit, 1) To UBound(Arr_Crit, 1)
Criterion = Arr_Crit(I, 1): Pos = 1
' Находим все вхождения строки и вытаскиваем индекс
Do
' Если Pos > 0, значит Criterion есть в массиве
Pos = InStr(Pos, strFromArr, Criterion)
If Pos = 0 Then Exit Do
' Определяем первый Null (vbNullChar 1 "vbNullChar")*
tmp = InStrRev(strFromArr, vbNullChar, Pos) - 1
' Определяем второй Null ("vbNullChar" 1 vbNullChar)* и выдергиваем индекс
N = Val(Mid$(strFromArr, InStrRev(strFromArr, vbNullChar, tmp) + 1, 7))
' P.S. Выдергивается что-то типа "1 https".
' Функция Val позволяет нам отбросит весь текст и оставить только 1!
' Константа "7" взята, т.к. максимальная строка в Excel равна 7 символам (1048576)
' Инкремент (+1) переменной Pos и замена данных в массиве Arr_Boolean по индексу {N}
Pos = Pos + 1: Arr_Boolean(N, 1) = True
' Подсчет количества строк для удаления
Count_DelRow = Count_DelRow + 1
'* в кавычках помеченный Null, который определяем!
Loop
Next I
' Вдруг удалять будет нечего)
If Count_DelRow = 0 Then Exit Sub
' Отключаем обновление экрана
Application.ScreenUpdating = False
' Переходим на нужный лист
Лист1.Activate
' Вставка обработанных данных с пометкой строк для удаления
Лист1.Range("B2").Resize(UBound(Arr_Boolean, 1), 1).Value = Arr_Boolean
' Включаем обновление экрана
Application.ScreenUpdating = True
End Sub
Код работает шустро, так что Вам данной производительности хватит!
P.S. Идею по детекту (обнаружению) нужных строк частично мне подсказал мой коллега! Но я её реализовала! Также отмечу, что чем больше критериев для удаления, тем эффективней алгоритм, по сравнению с алгоритмом на основе Brute Forse (полный перебор).
Если тема для Вас актуальна, то в свободное время слеплю алгоритм для удаления строк!
Тесты. Как быстро закрасить ячейки, AddressCut / AddressToMaxRanges. Функция для резки одного длинного адреса на блоки максимальной длины (~ 255 символов) с преобразованием в диапазоны
написал: ну, непосредственно к теме этот пункт как раз отношения не имеет, т.к. это подготовительный этап.
Ну как это? Это же валидация входных данных, как это отношения не имеет? Без неё алгоритм бесполезен и это является частью алгоритма!
Цитата
написал: с помощью функций (в конце) из этой моей темы .
Вся тема заточена под определение столбца, разве в ней присутствует подобное решение (сбор необходимых диапазонов строк)? Столбцы заранее же известны в вашем случае! Вы задаёте диапазон прямо в коде!
Тесты. Как быстро закрасить ячейки, AddressCut / AddressToMaxRanges. Функция для резки одного длинного адреса на блоки максимальной длины (~ 255 символов) с преобразованием в диапазоны
Предложу решения по ускорению кода на VBA (с xll соперничать нету смысла и некорректно), но для начала немного декомпозиции:
Алгоритм можно разделить на 3 пункта (рассматривала решение Jack Famous - Time total: 3,270 sec):
Получение адресов нужных ячеек и формирование одномерного массива;
Формирование единой строки с адресами (через Join);
Блочная закраска нужных диапазонов.
Цитата
Важно: Данное решение и так достаточно хорошо отрабатывает, тем более данная операция в большинстве случаев будет требоваться максимум 1 раз! Поэтому ниже представленная оптимизация, по факту не требуется, и представлена чисто с целью предоставить альтернативное решение!
Предложение по оптимизации 1 пункта (Результат 0,3, взамен 1,6):
Скрытый текст
Этот вариант у меня отрабатывает за 1.6 сек. и это примерно половина от полного времени всего алгоритма:
Код
tt = Timer: t = Timer
ReDim arr(Rows.Count / 2 - 1): i = -1
For r = 2 To Rows.Count Step 2
i = i + 1: arr(i) = Cells(r, 1).Resize(1, 10).Address(0, 0, xlA1)
Next r
Debug.Print "Adr by bedvit:", Format$(Timer - t, "0.0 sec") ' 1.6
Минус данного метода - цикличное обращение к объектной модели Excel, поэтому, желательно найти решение для отработки без взаимодействия с объектной моделью Excel в цикле! Можем это сделать следующим образом:
Код
tt = Timer: t = Timer
ReDim arr(Rows.Count / 2 - 1): i = -1
'---------------------------------------------------------------------------------------'
' Для начала определим границы первого диапазона, который мы хотим закрасить
Start = Cells(2, 1).Resize(1, 10).Address(0, 0, xlA1)
' Далее выдёргиваем номер строки. Например("A2:J2") = 2 или ("A555:J555") = 555
For r = 1 To Len(Start)
If IsNumeric(Mid(Start, r, 1)) Then NeedNumber = Val(Mid(Start, r)): Exit For
Next
arr(0) = Start: i = 0
' Заполнение массива интересующими адресами с помощью Replace
For r = 4 To Rows.Count Step 2
i = i + 1: arr(i) = Replace(arr(i - 1), NeedNumber, r)
NeedNumber = r
Next r
'---------------------------------------------------------------------------------------'
Debug.Print "Adr by Anastasia:", Format$(Timer - t, "0.0 sec") ' 0.3
Сильно попрошу не критиковать, только сегодня накидала пример! Главное - это демонстрация принципа, который можно оптимизировать для своих нужд!
2 пункт и 3 пункт без оптимизации (P.S. К успеху шла, но не получилось, не фартануло!):
Скрытый текст
Функция FILE_RangesFromAddress отрабатывает у меня примерно за 0,38. Переписывать данное решение на мой взгляд не имеет смысла, т.к. основная дорогая операция - Set arr(n) = sh.Range...
Можно конечно сделать подобное решение с обработкой индексов массива безSet arr(n) = sh.Range) и оно будет быстрее (0,2 против 0,38):
Код
'---------------------------------------------------------------------------------------'
t = Timer
Dim LB&, UB&, Len_Temp&, ii&, Line$, j&, v$, tmp1$, iii&, y$, arr_tmp$()
Dim sh As Worksheet: Set sh = ActiveSheet
LB = LBound(arr, 1)
UB = UBound(arr, 1)
Dim arr2() As String: ReDim arr2(UB)
i = 0: ii = -1
ReDim arr_tmp(1 To 64)
For r = LB To UB
x = arr(r)
Len_Temp = Len_Temp + Len(x) + 1
If Len_Temp > 256 Then
ReDim Preserve arr_tmp(1 To iii - 1)
tmp1 = Join(arr_tmp, ",")
ii = ii + 1
arr2(ii) = tmp1
Len_Temp = Len(y) + 1
arr_tmp(1) = y
arr_tmp(2) = x
iii = 2
Else
iii = iii + 1
ReDim Preserve arr_tmp(1 To iii)
arr_tmp(iii) = x
y = x
End If
Next r
If Not Len_Temp = 0 Then
ReDim Preserve arr_tmp(1 To iii)
tmp1 = Join(arr_tmp, ",")
ii = ii + 1
arr2(ii) = tmp1
End If
ReDim Preserve arr2(ii)
Debug.Print "Adr by Anastasia:", Format$(Timer - t, "0.00 sec") ' 0.2
'---------------------------------------------------------------------------------------'
Но Пункт №3 вынуждает нас обратиться к объектной модели Excel, а цикл в таком виде отрабатывает медленнее (~ на 0,3 медленее):
Код
For Each x In arr2
Range(x).Interior.Color = vbYellow
Next x
Чем в таком, т.к. цикл ниже заранее формирует единый массив типа Range, а выше описанное решение генерирует Range непосредственно в цикле:
Код
For Each x In FILE_RangesFromAddress(Join(arr, ","))
x.Interior.Color = vbYellow
Next x
Соответственно выигрывая в производительности в пункте 2 (0,18 сек.), мы проигрываем больше в пункте 3 (0,3 сек.). Разница = 0,12 сек!
Итог: Шалость удалась только с пунктом №1 (Итоговое время упало до 2,023)! В остальных случаях оптимизация не имеет смысла, т.к. Jack Famous, уже, на мой взгляд, выжал максимум + идёт упор на объектную модель, где программисты на VBA особой власти не имеют (остается только перебирать реализованные методы Microsoft или пользовательские надстройки и библиотеки на сторонних языках, если основная цель - это максимальная производительность в вычислениях).
написал: Есть у меня ещё интересное исследование по вопросу быстрого форматирования диапазонов
Сейчас просмотрела, очень много всего и так поняла, что эта тема является финальной) А в сообщении #12 последний и актуальный код по сравнению только на чистом VBA? Если да, то добавлю сегодня свой комментарий!
А что касается решений на Си. Это отлично, но с VBA сравнивать некорректно, так как процесс транслирования кода отличается (в случаи с Си или другим подобным языком, мы пользуемся заранее скомпилированным решением, а VBA хардкориться в RealTime (режиме реального времени), исключая промежуточную компиляцию в подобие байт-кода)! Но Вы Jack Famous, такого же мнения, как Я поняла , поэтому поздравляю, у Вас единомышленники!
eonka, добрый день, самый простой вариант, это активировать нужный лист, затем выделить ячейку:
Код
Sub Example()
Dim Sh
For Each Sh In ActiveWorkbook.Worksheets
Sh.Cells(1, 2) = "123"
Sh.Cells(1, 4) = "321"
Sh.Activate ' Добавлено
Cells(4, 1).Select
Next Sh
End Sub
написал: для удаления 1го символа есть отдельная функция в шапке темы. Обойти её вряд ли удастся.
Результат точно такой же String_DelLR_AscW_OneSym, как и в случае String_DelLR_AscW и от 100 символов в строке, преимущество за String_DelLR_InStr_Anastasia т.к. уже начинается разрыв в пользу InStr!
написал: 2 символа я тестил и мой метод, как минимум, не медленнее, а в ряде случаев быстрее
Тут возможно есть зависимость от железа и Вашему более новому/старому процессору в тандеме с ОЗУ по какой-то причине легче обработать алгоритм с массивом, нежели через InStr c Mid$! В любом случае, я копировала результаты с Immediate Window и обманывать мне нету смысла! А в каких ряде случаев при тесте на 2 символах метод String_DelLR_AscW шустрее?
Цитата
написал: К тому же есть ещё пространство для ускорения,
Это неплохо, будет интересно просмотреть! А то мельком пробежалась по форуму и особо интересных тем нету. То бесконечные просьбы помощи в коде, то узконаправленные решения, которые мне либо не интересны, либо я в них просто не шарю)
Промежуточный итог таков, что алгоритмы неоднозначные и всё зависит от входных данных и поставленных задач:
Если задача состоит в том, чтобы удалить 1-2 символа в начале или конце строки, то метод String_DelLR_InStr, основанный на Mid$ самый лучший вариант, независимо от длины строк #39!
Если задача состоит в том, чтобы удалить >2 символов, то тут можно использовать метод String_DelLR_AscW, но с оговорками, т.к. если обрабатываемые строки небольшие, (<9000) тогда метод в небольшом выигрыше, если нет, то в проигрыше!
Занимая нейтральную позицию, без предвзятости, от себя я бы рекомендовала использовать String_DelLR_InStr! Код небольшой, интуитивно понятный и в тестах особо не уступает и бывает даже лучше отрабатывает! Плюс многим логика String_DelLR_AscW может быть не понятной (я например не сразу допёрла, в чём подвох)!
БМВ, приношу извинения, думаю это отвлечение от темы было последним, т.к. по последним сообщениям Jack Famous, думаю узнал всё, что ему хотелось об новичке на форуме!
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...
Это потому, что начальная длина пустой строки, которая впоследствии наращивается имеет начальную длину в один пробел:
Код
line = " "
И при первой же итерации Вам нужно вставить 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 год) и никаких претензий я не Высказываю (на момент публикации я вообще не имела никаких дел с программированием😁)! Только поделилась своими мыслями и указала на недочёт в алгоритме! Буду признательна аргументированной критике в мой адрес, если это имеет место быть!
Но мы отвлеклись от темы... Многое написано и в принципе всё разобрано на достаточном уровне, поэтому предлагаю Вам Jack Famous, сделать вывод и подвести черту по поводу данной темы, т.к. последнее решение хоть и моё, но с натяжкой, ведь я его не придумала с нуля, а оптимизировала готовое!
написал: Aнaстaсия , так вы эти листинги на заводе кодите — я правильно понимаю?
Да, всё верно, кодинг для обработки данных, автоматизации и прочих задач, который требует заказчик!
Цитата
написал: Excel вам нужен по работе или просто хобби?
Ну как сказать... по сути у меня должность, не связанная с программированием, но т.к. у меня это получается, то меня грузят как раз только этими задачами! Можно сказать, что для работы это не требуется и я могла просто спокойно выполнять свою работу, но один раз помогла по-дружбе и пошло поехало, сарафанное радио... Более сказать не могу, дабы не деанонимизировать себя!
Цитата
написал: Как обучались VBA?
Тупо взялась помочь человеку и сделала ему простой макрос, который помог ему! Далее курсы с теневых ресурсов, книги, какие-то решения на форумах, тесты и разбирательства в чужом коде! В общем, стандартный путь и полное погружение с задротством (~ 8-11 часов в день с перерывами)! Ещё добавлю, что нужно подтягивать высокоуровневую базу (алгоритмы, структуры данных, определения и формулировки), т.к. на просторах интернета много мусора и отсебятины! Ну и конечно никому не верить, т.к. большинство информации в современном мире просто воруют заимствуют, что требует дополнительной проверки и разбирательства, т.к. не вся заимствованная информация может быть истиной! Как итог:
Тотальное задродство (одаренности нету и это миф, тупо Hard Work и заинтересованность решают);
Помнить, что кругом, в основном, обман и нужно всё проверять и тестить!
написал: Напишу простыню об истории метода, пока время есть)
Эх Лёха, Лёха Лёха, мне с тобой так плохо, душно в комнате как стало и дышать тяжко стало...😁 (без обид)
Ну и Вы расписали конечно, но всё зря, т.к. в большинстве случаев люди сразу оправдываются! А я сделала вывод как раз на основании ваших высказываний и неверных предположений... Ну в прочем кто шарит, тот поймёт!
Цитата
написал: абсолютно беспочвенны (в который раз)
Ну это только Ваше мнение, не прикреплённое фактами! Тупо на основании своей истории, которая может быть ложной, Вы сделали вывод... Ну дело ваше!
Цитата
написал: расскажите, пожалуйста, коротко о себе: чем занимаетесь, сколько программируете на VBA, бывали ли на других форумах и, самое интересное, почему именно мои темы?
Тут особо рассказывать нечего, т.к. я начинающая только (~3 года увлекаюсь), работа на заводе, на других форумах не была, вот только недавно решила зарегаться и попробывать что-то сделать. На вас попала, т.к. сама на начальном этапе пыталась всё ускорить до каждой наносекунды, но Вы меня ботом назвали и как-то агрессивно с модератором на меня наехали, начали удалять мои сообщения и вообще в старую учётку зайти не смогла! Пришлось новую открывать, но т.к. к старой я так понимаю доступа не будет (после этой статьи вообще может и эту заблочат, тогда смысла регистрировать заново уже не будет и мы с Вами попрощаемся)!
Цитата
написал: Думаю, не мне одному будет интересно, где пропадал всё это время хороший VBAшник.
Я бы не назвала себя VBAшником и программистом тоже, скорее кодером, который только вкатывается и начинает разгребать весь информационный мусор и искать истину!
Ладно! Давайте конструктивно перейдём к теме и поставим логическую точку. А пока жду комментарии по поводу кода!
Ну и для размышления, хочу Вас огорчить, но когда задача стоит в том, чтобы удалить 1 или 2 символа (sList$ = "." или sList$ = "./"), что в принципе можно прировнять к реальным условиям, то моё решение отрабатывает также, а в большинстве случаев, быстрее:
Код
Private Sub Test_String_DelLR_10()
Dim aL() As Byte, aR() As Byte
Dim s$, s2$, sList$, List(), t!, n&
sList$ = "."
'sList$ = "./"
s = "sdgfsgsdfsdfsdfsdfsdf"
s = String$(10, "Q")
s = sList & s & StrReverse(sList)
String_UniList_ToArr1D_ExistsInFull aL, sList: aR = aL
t = Timer
For n = 1 To 1000000
'String_DelLR_AscW s, s2, aL, aR, True, True ' 0.5 | 0.5
'String_DelLR_InStr_Anastasia s, s2, sList, sList, True, True ' 0.4 | 0.5
Next n
Debug.Print Format$(Timer - t, "0.0"), s2
End Sub
Что собственно говоря доказывает правоту моих слов:
Цитата
написал: Да, конечно, если добавить весь Unicode, то Ваш алгоритм будет отрабатывать быстрее, т.к. проход осуществляется по массиву, а у меня каждый раз бы дергался символ для проверки через Mid$
То есть, чем больше символов Вы захотите удалить, тем быстрее Ваш алгоритм будем отрабатывать по сравнению с моим, но при условии, что длина строки будет небольшой! В противном случае алгоритм будет хуже себя показывать!
Jack Famous, слабая корреляция у Вас в ваших логических умозаключений, ну серьёзно!
Цитата
написал: тестовый стенд — это что-то, что можно скопировать в модуль, запустить нужную процедуру и увидеть время работы и результат.
Скачивайте файл и запускайте, даже вставлять ничего не нужно! Могу в принципе заняться этим вопросом и раскидать, что такое тестовый стенд и что им может являться, т.к. думаю Вы сами это определение сформировали только на основании своих когнитивных искажений и личного опыта!
Цитата
написал: (не у компа)
Так как Вы собирались тестить то, если компа нету? Для ознакомления, я код метода выложила. Описала всё подробно! Без компа Вам остается только анализировать данное решение! Всё, лучше не придумаешь)
Цитата
написал: сознательно упустили, забыли или не знали?
Выберу то, что Вы хотите услышать! Я не знала! Только в чём проблема то, если человек не знает? Проблема, если человек не признает ошибки и не учиться!
В целом вывод из статьи можно сделать такой:
У Вас возникла задача по удалению ненужных символов в строке (слева/справа)!
Вы искали решение и наткнулись на решение по принципу битового массива маску! Как говорил Владимир
Цитата
написал: Это достаточно распространённый прием.
Вы, на основании распространённого приёма съагрегировали решение, но досконально не разобрались в нём (на это указывают Ваши высказывания):
Цитата
написал: тоже попробуйте и покажите. Спойлер: результат вам не понравится
Скорее всего Вы попытались сделать что-то, и у Вас не вышло (хотя описание было довольно прозрачным и метод интуитивно понятный (без преувеличения я за 3-4 минуты примерно внесла изменения в этот метод)
Цитата
написал: Сильно сомневаюсь, что вариант на индексах массива мог проиграть — наверное, вы что-то перепутали.
Нет, это Вы не до конца разобрались в своём алгоритме, и поставили под сомнение мой алгоритм!
В общем, вместо того, чтобы признать, что я сделала неплохое решение и пожать друг другу руки (образно), Вы душите меня странными вопросами и причём уже наговорили так много, что подвергает сомнением, действительно ли Вы автор алгоритма! Или может это видоизменённая копипаста!?
Да, конечно, если добавить весь Unicode, то Ваш алгоритм будет отрабатывать быстрее, т.к. проход осуществляется по массиву, а у меня каждый раз бы дергался символ для проверки через Mid$, но оно надо это? + Эта задача лишена логики для применения в реальной жизни!
написал: Сильно сомневаюсь, что вариант на индексах массива мог проиграть — наверное, вы что-то перепутали.
Да что же Вы всё сомневаетесь то) И тесты элементарные, без подвоха, тупо генерируется строка с одним символом разной длины:
Код
s = sList & string$(100,"Q") & StrReverse(sList)
s = sList & string$(1000,"Q") & StrReverse(sList)
s = sList & string$(5000,"Q") & StrReverse(sList)
s = sList & string$(10000,"Q") & StrReverse(sList)
s = sList & string$(32767,"Q") & StrReverse(sList)
И далее просто пропускается строка через цикл с заранее подготовленными методами:
Код
For n = 1 To 1000000
'String_DelLR_AscW s, s2, aL, aR, True, True
'String_DelLR_InStr_Anastasia s, s2, sList, sList, True, True
'String_DelLR_InStr_Update s, s2, sList, sList, True, True
'String_DelLR_Bytes s, s2, sList$
'String_DelLR_InStr s, s2, sList, sList, True, True
Next n
По поводу "Сильно сомневаюсь, что вариант на индексах массива мог проиграть..." Вы упускаете одну важную деталь в вашем алгоритме, а именно:
Код
aBt = sIn ' Преобразование строки в байт-массив
Это преобразование также влияет на производительность кода, и чем длиннее строка, тем медленнее будет конвертация! Но убрать его нельзя, т.к. логика построена именно на этом!
Цитата
написал: И да, строка не обязательно может быть ограничена возможностями ячейки. Строки можно получать не только с листа.
За это я в курсе, просто мы рассматриваем алгоритм вроде для решения реальных задач, а реальные задачи в основном у большинства связаны с данными именно на листе Excel!
Цитата
написал: добавлял к ним список символов для удаления слева и справа (вариант без символов, с 1, с10ю, с обоих сторон, с одной из)
На результат тестирования это никак не повлияет! У вас стоят булевые идентификаторы, которые определяют, нужно ли удалять символы с той или иной стороны! Простыми словами: Вы хотите удалить с обоих сторон символы, то заняло бы это примерно 1 условную единицу! Далее хотите удалить только слева или права, то результат будет составлять ~0.5 (противоположный алгоритм просто не запуститься), без символов не вижу смысла запускать, тогда зачем алгоритм сам), Но если так нужно, то все алгоритмы выдадут результат приближенный к 0, т.к. сама логика не выполниться, за исключением алгоритма, требующих преобразование в байт-массив:
Код
aBt = sIn ' Это замедлит вашу основную процедуру!
' А практически всё ниже не выполниться!
If fL Then ' пропуск
For b = 0 To UBound(aBt) Step 2
If aL(aBt(b) + (256 * aBt(b + 1))) = 1 Then l = l + 1 Else Exit For
Next b
End If
If l = ll Then sOut = "": Exit Sub
If fR Then 'пропуск
For b = UBound(aBt) - 1 To 0 Step -2
If aR(aBt(b) + (256 * aBt(b + 1))) = 1 Then r = r + 1 Else Exit For
Next b
End If
Можно дополнительно добавить инструкцию на проверку шаблонной строки, но не вижу смысла, т.к. скорее всего никто алгоритм не будет запускать с пустой строкой-шаблоном. Это же бред! Но Вашему алгоритму это не помешает, чтобы ненужное преобразование изолировать, на случай, если найдется такой нехороший человек 😁
Цитата
написал: Про потерянный 2ой байт так ничего и не увидел.
И не увидите))) Вы ранее сами добавили проверку же! Зачем писать велосипеды)?
Алгоритм построен на принципе полного перебора и данная строка служит критерием для определения, входит ли просматриваемый символ в перечень шаблонных символов! Добавлю комментарий:
Код
...
' Первый цикл перебирает все байты исходной строки слево-направо
For I = LB1 To UB1 Step 2
' Второй цикл перебирает все байты строки с символами, которые нужно удалить
For J = LB2 To UB2 Step 2
' Если просматриваемый байт совпадает с байтом символа для удаления, то делаем Flag = True и выходим из цикла,
т.к. мы убедились что данный символ нужно удалить и можно переходить к проверке следующего | В противном случае мы увеличиваем счетчик на 1
If SB(I) = SB1(J) Then Flag = True: Exit For Else N = N + 1
Next J
' Если Flag = True, тогда обнуляем счетчик и меняем состояние флага, т.к. мы понимаем, что символ нужно удалить и требуется проверить следующий
' Если Flag = False, тогда сверяем счетчик с количеством символов для перебора, то есть убеждаемся, что например N = 7,
' и символов для удаления в шаблоне тоже 7. Len("./|\_-?") = 7. Можно реализовать и без счетчика, но счетчик был добавлен с целью
' убедится в правильности логики алгоритма!
If Not Flag Then If N = NN& Then Exit For Else N = 0 Else N = 0: Flag = False
Next I
...
Цитата
написал: тоже попробуйте и покажите. Спойлер: результат вам не понравится
Ну как сказать... Мне всё понравилось. Видимо немного Вы по-другому реализовали моё предложение. Результаты всех методов ниже:
Код String_DelLR_InStr_Update - это ваш обновленный код в #30 комментарии. Код String_DelLR_InStr_Anastasia:
Код
Sub String_DelLR_InStr_Anastasia(sIn$, sOut$, Optional sL$, Optional sR$, Optional fL As Boolean, Optional fR As Boolean)
Dim ll&, l&, n&, I_S&, I_F&
ll = Len(sIn)
If ll = 0 Then Out = sIn: Exit Sub
I_S = 1
I_F = ll
If fL Then
Do Until (InStr(sL, Mid$(sIn$, I_S, 1)) = 0)
I_S = I_S + 1: If I_S > I_F Then Out = "": Exit Sub
Loop
End If
If fR Then
Do Until (InStr(sR, Mid$(sIn$, I_F, 1)) = 0)
I_F = I_F - 1: If I_S > I_F Then Out = "": Exit Sub
Loop
End If
sOut = Mid$(sIn, I_S, I_F - I_S + 1)
End Sub
Тесты соответственно на разных входных данных (разная длина строк):
Как можем заметить, то использование одного Mid$ эффективнее + мы не меняем исходную строку, что значительно замедляем все вышеперечисленные алгоритмы с использованием InStr, кроме 2-x моих! Также отмечу, что при увеличение входных данных, алгоритмическая сложность растет нелинейно! Особенно это видим у методов String_DelLR_InStr и String_DelLR_InStr_Update. Меньшее отклонение от линейной сложности алгоритма мы наблюдаем у метода String_DelLR_InStr_Anastasia. На начальных тестах (до Len(s) = 5000 включительно) мы вообще имеем константную сложность алгоритма!
Как итог: Чтобы не париться, можно использовать String_DelLR_InStr_Anastasia и не таскать с собой дополнительно код String_UniList_ToArr1D_ExistsInFull для метода String_DelLR_AscW. Стоит признать, что данный метод шустрый, но незначительно! Но для любителей Performance (максимально быстрых решений) можно конкатенировать два решения в что-то подобное (добавить промежуточный код):
Код
ll = Len(sIn)
If ll = 0 Then Out = sIn: Exit Sub
If ll > 5000 Then
String_DelLR_InStr s, s2, sList, sList, True, True
Else
String_DelLR_AscW s, s2, aL, aR, True, True
End If
Под String_DelLR_InStr, в данном примере, я имею ввиду метод String_DelLR_InStr_Anastasia, просто убрала имя чтобы не говнокодить 😁
Без негатива, только конструктивная критика! Каждый раз, при каждой итерации Вы меняете строку методами Left$ и Right$! Это очень негативно сказывается на производительности при больших строках, тоесть если отойти от примера в коде и протестировать его на более большом примере, то Вы получите проседание в производительности! И мой код в выигрыше! Соответвено алгоритм лучше переписать на счетчики, которые будут запоминать текущее состояние строки!
Так Вы будете работать только с одной строкой, но при этом не будете её изменять, т.к. изменения строк - это довольно дорогие операции! Мой алгоритм на этом и основан и получение подстроки вычисляется только один раз!
написал: • вылетит в дебаг при переданной пустой строке • вылетит в дебаг при переданной строке, полностью состоящей из символов для удаления • не может работать с разными списками для начала/конца, а также выборочно удалять только с начала/конца. • не учитывает второй байт символов при проверке.Это означает, что символы с ненулевым вторым байтом дадут ложное совпадение. • избыточная проверка: If UBound(SB, 1) Mod 2 Then, т.к. a() As Byte = s это всегда (насколько мне известно) от 0 до 2 * Len(s) -1, то есть UBound(SB, 1) — это всегда нечётное число.
Открою окно, а то душно стало! Это всё решается парочкой If-ов и задача с моей стороны была предоставить альтернативную логику решения задачи, а не выдать отполированное решение с учетом обработки всех ситуаций (потраченное время на составление кода - не более 1 часа)!
По поводу "избыточная проверка"... Согласна, можно так
Код
x = UBound(SB, 1) - 1
Просто экспериментировала с байтами и строками, в итоге не исправила старый вариант, где тесты проводила с массивом символов на финальный вариант с байт-массивом!
Цитата
написал: Код сильно неочевидный, сложный
Ну для кого как:
Конвертируем исходные данные и строку-шаблон в байт-массивы;
Перебираем эти массивы по нечетным индексам и выходим из цикла, когда убеждаемся, что просматриваемый байт не входит в состав байтов-шаблона!
На основании сохранившихся счетчиков циклов, выдергиваем нужный текст!
Всё, иззи-пизи! Полный перебор (Brute Forse) по сути!
Цитата
написал: при этом полно абсолютно ненужных действий (в циклах ещё есть) и присутствуют ошибки
Каждое действие важно для решения поставленной задачи, за исключением избыточного IF, и выше я прояснила это и согласилась! Но вопрос я всё же должна задать) Приведите, пожалуйста примеры, где полно ненужных действий "(в циклах ещё есть)" и ошибок! А то такие заявления попахивают воздуханством (без негатива)!
И спасибо конечно за не новичка, но попрошу не льстить!
Marat Ta, приветствую, тут на самом деле всё просто! Просто кажется что сложно, но Вы правы в одном, что данная тема не относится к Excel, а больше про "алгоритмическое задродство"!
Объясню своими словами: У вас есть например строка "-пример//" И вам необходимо удалить лишние символы в начале и конце по шаблону (в данном случае это "-","/") чтобы получить на выходе голую строку "пример"! И все вышеперечисленные алгоритмы в принципе этим и занимаются!
Но также дополню, что если в примере будет "-при-ме/р//", то на выходе Вы получите "при-ме/р", т.к. алгоритм заточен только на начало и конец строки!
Надеюсь объяснила понятно) Если нет, то пожалуйста дайте знать, я попытаюсь ещё раз 😁
написал: Aнaстaсия , совсем забыл — ваш код принимает только один список, а остальные испытуемые могут работать с разными списками символов для удаления слева и справа. А также могут удалять только слева/справа.
Добавить два If и два булевых аргумента в начале метода, похоже это действительно проблема 😁
Jack Famous, прощайте и в коде разберитесь, прежде чем негативно аргументировать!
Что же для Вам тогда конструктивно)? Похоже у нас разные понятия! У Вас конструктивно - это когда Вам выгодно похоже!
Цитата
написал: Прощание с вами произошло из-за того, что вы грубо нарушили правила форума, создав клон учётной записи — теперь вас забанят (и все новые клоны тоже).
Правильное решение для немного шаряших новичков (это сарказм)
Цитата
написал: Создавать массивы для работы моей функции нужно только один раз перед прогоном. Подразумевается, что все строки нужно проверить по одному и тому же списку символов — это реальная задача. Интересно, как вы представляете себе иной вариант работы.
Ваша реальная задача и описание имеют слабую корреляцию! Переделать тесты тогда нужно, но это опять не конструктивно!
Ладно, я больше не буду говорить своё мнение на этом форуме, а Вам посоветую сходить на курсы гуглёжки и подтянуть базу (я серьёзно), чтобы людей не путать!