Страницы: 1
RSS
Вложенные циклы долго работают, Оптимизация
 
Всем привет!

Есть задача перенести данные из одной таблицы в другую по условию (можно сказать то же самое что ВПР)
На данный момент код работает следующим образом: ищу совпадения по первому столбцу с помощью вложенных циклов.

На малом объеме данных это вполне рабочая схема, но в действительности у меня 12 тысяч строк в первой таблице, и 500 тысяч строк во второй. Очевидно, что это работает очень долго.

Есть какие то способы уйти от вложенных циклов?

Слышал, что использование коллекций или словарей куда быстрее, но как реализовать конкретно в этой задаче, я не понимаю

Очень надеюсь на вашу помощь)
Код
Sub Qft()

Dim arr1 As Variant
Dim arr2 As Variant     

arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange  'массив, куда нужно внести данные

arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange  'массив, из которого нужно забрать данные

Dim i As Long, g As Long


For i = 2 To UBound(arr1)
    For g = 2 To UBound(arr2)
        If arr1(i, 1) = arr2(g, 1) Then
            arr1(i, 5) = arr2(g, 2)
            arr1(i, 9) = arr2(g, 3)
            arr1(i, 11) = arr2(g, 4)
        End If
    Next g
Next i


Sheets("Лист1").Cells(1, 1).Resize(UBound(arr1), UBound(arr1, 2)) = arr1

End Sub
 
как минимум нужно выходить из второго цикла при совпадении а не продолжать крутить
но лучше
первый цикл по массиву 2 заполняет словарь с первым индексом массива  
вторым циклом  по нему ищется то что нужно и определяется индекс после чего заносятся по нему значения

Тоже самое можно с коллекцией
Изменено: БМВ - 28.02.2024 21:53:39
По вопросам из тем форума, личку не читаю.
 
rktagirov2 Здравствуйте Ну зачем сразу все на вложенные циклы валить. Выхода из цикла нет, о чем написал БМВ. Вообще ничего не отключили даже обновление экрана. Надо почитать здесь. Раз применен UsedRange надо проверять в книге не захватывает ли UsedRange пустые строки и столбцы. Но здесь судя по исходным данным лучше применить CurrentRegion. Можете поэкспериментировать код со словарем. Если будут в UsedRange пустые строки и столбцы будет ошибка проверку на пустые значения не сделал.
Код
Sub enstaralgfjh()
Dim Arr1, Arr2, i As Long, Dic1
Application.ScreenUpdating = False
Set Dic1 = CreateObject("Scripting.Dictionary")

Arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange
Arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange

For i = 2 To UBound(Arr1): Tp1 = Dic1(Arr1(i, 1)): Next i

For i = 2 To UBound(Arr2)
If Dic1.Exists(Arr2(i, 1)) Then Dic1(Arr2(i, 1)) = i
Next i

For i = 2 To UBound(Arr1)
    Arr1(i, 5) = Arr2(Dic1(Arr1(i, 1)), 2)
    Arr1(i, 9) = Arr2(Dic1(Arr1(i, 1)), 3)
    Arr1(i, 11) = Arr2(Dic1(Arr1(i, 1)), 4)
Next i

Sheets("Лист1").Cells(1, 1).Resize(UBound(Arr1), UBound(Arr1, 2)) = Arr1

End Sub
Изменено: Евгений Смирнов - 29.02.2024 12:41:38
 
Евгений Смирнов, в основном файле у меня отключены автообновление экрана и тд.
Спасибо большое, я попробовал, все работает, и очень быстро. И подскажите пожалуйста, что в вашем коде Tp1?
Можете, пожалуйста, кратко прокомментировать ваш код, чтобы было понимание. Еще раз спасибо)
 
БМВ, хорошее замечание, спасибо. Упустил этот момент
 
rktagirov2
Цитата
Можете, пожалуйста, кратко прокомментировать ваш код, чтобы было понимание
Создаем словарь Dic1
Переносим данные с листов в переменные Arr1 Arr2
1 цикл заполняем ключи словаря значениями с 1 листа 1 столбца. (Tp1 переменная типа вариант. Забыл ее объявить Так записывается один из методов заполнения словаря ключами со значениями Emtpy)
2 цикл заполняем значения словаря номерами нужных строк со второго листа
3 цикл меняем значения в Arr1 найденными значениями с массива Arr2
Последние строки выгрузка на лист измененного массива Arr1
В принципе по моему можно по скорости еще лучше сделать, но ТС видимо на глазок замеряет. Конкретных результатов замеров  нет.
 
Цитата
Евгений Смирнов написал:
записывается один из методов заполнения словаря ключами со значениями Emtpy
А зачем, не проще так
Код
Sub enstaralgfjh()
Dim Arr1, Arr2, i As Long, Dic1
Application.ScreenUpdating = False
Set Dic1 = CreateObject("Scripting.Dictionary")

Arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange
Arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange

For i = 2 To UBound(Arr2)
    If Not Dic1.Exists(Arr2(i, 1)) Then Dic1.Add (Arr2(i, 1)), i
Next i

For i = 2 To UBound(Arr1)
    If Dic1.Exists(Arr1(i, 1)) Then
        Arr1(i, 5) = Arr2(Dic1(Arr1(i, 1)), 2)
        Arr1(i, 9) = Arr2(Dic1(Arr1(i, 1)), 3)
        Arr1(i, 11) = Arr2(Dic1(Arr1(i, 1)), 4)
    End If
Next i
Sheets("Лист1").Cells(1, 1).Resize(UBound(Arr1), UBound(Arr1, 2)) = Arr1

End Sub

УПС, не внимательно прочитал первое сообщение, во второй таблице 500 тыс, тогда всё верно, но удалять не буду, пусть будет как вариант
По скорости ТС пусть отпишется
Изменено: Msi2102 - 29.02.2024 17:53:45
 
Msi2102
Цитата
А зачем, не проще так
Так проще.Я сильно не думал как проще.Что в голову сразу пришло так написал. Ведь мы не заказ делаем. Причем вопрос спорный какой по скорости будет лучше. У меня таких больших файлов нет, чтобы побаловаться и сравнить скорости
 
Цитата
Евгений Смирнов написал:
Причем вопрос спорный какой по скорости будет лучше.
Согласен отписался выше, не посмотрел, что во второй таблице 500 тыс
 
Msi2102
Цитата
Согласен отписался выше, не посмотрел, что во второй таблице 500 тыс
Вот я наверно по интуиции  поэтому так и сделал
 
Я бы, на месте ТС, глянул в сторону PQ
 
вариант SQL запросом:
Скрытый текст
Изменено: artemkau88 - 01.03.2024 21:11:42
 
Цитата
artemkau88 написал:
Ещё вариант:
с теми же ошибками :-)

Цитата
Msi2102 написал:
Я бы, на месте ТС, глянул в сторону PQ
а я б на название темы глянул, там четко сказано о том что циклы медленны, а не чем заменить макрос.
По вопросам из тем форума, личку не читаю.
 
Цитата
БМВ написал:
а я б на название темы глянул,
так, это только на стадии размышлений, я же не предлагаю решение, а советую как можно ОПТИМИЗИРОВАТЬ процесс :D
 
БМВ, да, спасибо! Я не заметил, что у автора темы тот же код (конец дня видно сказался)
Изменил алгоритм на SQL запрос в сообщении выше
Изменено: artemkau88 - 29.02.2024 20:47:29
 
Евгений Смирнов, Ваш код работает за пару минут (до 10 мин), а вложенные циклы - несколько часов)
 
Всем большое спасибо за помощь. Удивлён, что так быстро решили вопрос.На самом деле, задачка немножко сложнее, вечером скину файл, максимально приближённый к оригиналу (оригинал не могу кинуть, тк он с работы и содержит конфиденциальные данные)
 
artemkau88,
на будущее
Код
    Set oRange = .Range(.Cells(1, 1), .Cells(lr, 4))
    generalRange = "[" & .Name & "$" & oRange.Address(0, 0) & "]"
может злую шутку сыграть, если количество строк будет более 64К или 32К точно нек помню, Еслди брать весь столбец, то работает, а если нужно часть листа - то ограничено.

на коллекции , у меня чуть быстрее чем на словаре, но на большом количестве проигрывает варианту #3

Код
Dim mCol As New Collection
Dim arr1 As Variant
Dim arr2 As Variant
arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange
arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange
Dim i As Long, g as long
On Error Resume Next
For i = 2 To UBound(arr2)
    mCol.Add i, arr2(i, 1)
Next
Err.Clear
For i = 2 To UBound(arr1)
            g = mCol.Item(arr1(i, 1))
            If Err = 0 Then
                arr1(i, 5) = arr2(g, 2)
                arr1(i, 9) = arr2(g, 3)
                arr1(i, 11) = arr2(g, 4)
            Else
                Err.Clear
            End If
Next
Sheets("Лист1").Cells(1, 1).Resize(UBound(arr1), UBound(arr1, 2)) = arr1
Изменено: БМВ - 01.03.2024 19:55:34
По вопросам из тем форума, личку не читаю.
 
БМВ, спасибо! Правильно ли я понял, что это ограничение для диапазона для запроса SQL? Спасибо!
Изменено: artemkau88 - 01.03.2024 13:51:43
 
Цитата
artemkau88 написал:
что это ограничение для диапазона для запроса SQL?
да, может поставить эксперимент на одном столбце и посчитать сколько запрос SELECT * вернет при всем столбце во FROM и при например 90000 строках. если все будет заполнено на 99999 строк.
По вопросам из тем форума, личку не читаю.
 
БМВ, большое Вам спасибо!  Сегодня попробую.
Попробовал на 99000 строк. Выскочила ошибка (см. вложение).
При изменении диапазона до меньшего размера (например до 60000, либо явного указания всего столбца, типа Лист1$"A:K"), ошибка не выскакивает.
Excel 2007 версии
Спасибо большое за ответ! :)
Изменено: artemkau88 - 01.03.2024 17:33:14
 
Ну наверно надо на скорость и это проверить.
Немного кода у БМВ украл наверно выговор будет :D
Код
Sub enstaraldfhg()
Dim Arr1, Arr2, i&, g&, Col1 As New Collection
Arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange
Arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange
For i = 2 To UBound(Arr1): Col1.Add i, VBA.CStr(Arr1(i, 1)): Next
On Error Resume Next
For i = 2 To UBound(Arr2)
g = Col1.Item(Arr2(i, 1))
    If Err = 0 Then
    Arr1(g, 5) = Arr2(i, 2)
    Arr1(g, 9) = Arr2(i, 3)
    Arr1(g, 11) = Arr2(i, 4)
    Else: Err.Clear
    End If
Next
Sheets("Лист1").Cells(1, 1).Resize(UBound(Arr1), UBound(Arr1, 2)) = Arr1
End Sub
 
Цитата
Евгений Смирнов написал:
выговор будет
будет. :-) чуть подправил, ибо при не уникальности на втором первом листе будет ошибка. Так лучше, но все равно медленнее. чем тоже на словаре.

Код
Sub enstaraldfhg()
Dim Arr1, Arr2, i&, g&, Col1 As New Collection
Arr1 = ThisWorkbook.Worksheets("Лист1").UsedRange
Arr2 = ThisWorkbook.Worksheets("Лист2").UsedRange
On Error Resume Next
For i = 2 To UBound(Arr1): Col1.Add i, VBA.CStr(Arr1(i, 1)): Next
Err.Clear
For i = 2 To UBound(Arr2)
g = Col1.Item(Arr2(i, 1))
    If Err = 0 Then
    Arr1(g, 5) = Arr2(i, 2)
    Arr1(g, 9) = Arr2(i, 3)
    Arr1(g, 11) = Arr2(i, 4)
    Else: Err.Clear
    End If
Next
Sheets("Лист1").Cells(1, 1).Resize(UBound(Arr1), UBound(Arr1, 2)) = Arr1
End Sub
Изменено: БМВ - 01.03.2024 22:43:14
По вопросам из тем форума, личку не читаю.
 
БМВ
Цитата
ибо при не уникальности на втором листе будет ошибка
Вы подправили, чтобы не было ошибки при не уникальности на 1 листе. Первый цикл по  первому листу.
 
да, по первому, это я промахнулся.
По вопросам из тем форума, личку не читаю.
Страницы: 1
Наверх