Как присвоить значения из массива из 2 столбцов массиву из одного столбца на основе столбца, соответствующего определенным критериям

Мне нужно сделать макрос, который будет собирать номера деталей из столбца А и вставлять их на другой лист через каждые 8 ​​пробелов. Загвоздка в том, что мне нужно сделать это на основе кодов заказов: A11, A21, A31, B11, B21, B31, C11, C21, C31, C12, C22, C32, C13, C23, C33 (находится в столбце B) на лист, Есть 5 листов, которые сгруппированы следующим образом: Лист «A##» содержит все коды, начинающиеся с «A». Лист «B##» содержит все коды с «B». Лист «C#1» содержит все коды, начинающиеся с C и заканчивающиеся на 1 и так далее. Это необходимо сделать примерно для 12000 деталей. Из того небольшого знания, что у меня есть в Excel VBA, я считаю, что массив — это самый быстрый способ сделать это.

Примером того, как выглядит код заказа, может быть «A11», «A12», «A13» для 3 кодов, которые необходимо отправить на другой лист. Я использовал подстановочные знаки, чтобы ограничить фильтрацию (например, «A**» для обозначения «A13», «A23» и т. д.).

Как присвоить значения из массива из 2 столбцов массиву из одного столбца на основе столбца, соответствующего определенным критериям

Ниже приведен код, который я сейчас использую для выполнения этой задачи, а с другими макросами и всеми циклами первый запуск макроса занял у меня 1 час 5 минут. Однако этот макрос нужно будет запускать раз в месяц и с той же рабочей книгой, поэтому я запустил второй раз, чтобы «обновить» данные, и это заняло 3,5 часа. Теперь он больше не будет работать, поэтому мне пришлось искать другие способы его ускорения.

В следующем коде wb = активная рабочая книга, а Sht — это лист, на котором я хочу использовать коды. Я написал это так, потому что делаю это надстройкой Excel, а не просто модулем в рабочей книге.

Public Sub SetupSheetA()
Set wb = ActiveWorkbook
Set Sht = wb.Worksheets("A##")
Code = "A**"
'Grab endRow value for specific sheet designated by the order code
With wb.Worksheets("SO Hits Data Single Row")
    endRow = 1 + 8 * Application.WorksheetFunction.CountIf(.Range("B4:B999999"), Code)
End With
Sht.Cells.Clear 'Clear sheet contents

'Macros
    Call PartInfo

    'Other macros not relevant to this question

End Sub
Public Sub PartInfo()
'***********************************************************************************************************
'Collect Part #, order code, vendor info, and WH Info
'***********************************************************************************************************
Dim j As Long, i As Long
j = Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A999999"))
With Sht
    'Part #
    CurrentPartRow = 2
    For i = 4 To j
        If Sheets("SO Hits Data Single Row").Range(Cells(i, 2).Address) Like Code Then
            .Range(Cells(CurrentPartRow, 1).Address).Value = "='SO Hits Data Single Row'!" & Cells(i, 1).Address
            CurrentPartRow = CurrentPartRow + 8
        End If
    Next i
    'Order code
    .Range("A3").Value = "=VLOOKUP(A2,'SO Hits Data Single Row'!$A:$B,2,FALSE)"
'Copy to Next Row
    For CurrentPartRow = 10 To endRow - 7 Step 8
        'Order code CopyPaste
        .Range("A3").Copy Destination:=.Range(Cells(CurrentPartRow + 1, 1).Address
    Next CurrentPartRow
End With
End Sub

Я попытался ускорить процесс, сохранив книгу в формате .xlbs, что уменьшило размер файла с 240 МБ до 193 МБ. Затем я удалил все данные, которые мне сошли с рук, и удалил все ненужное форматирование, которое еще больше уменьшило размер файла до 163 МБ, а затем удаление листов, на которые макрос вставляет данные, уменьшило размер файла до 73 МБ. Даже с этим гораздо меньшим файлом макрос все равно будет зависать и не отвечать, несмотря на то, что он работал в течение всех выходных.

Я также попытался отфильтровать массив, используя этот код:

Dim arr1 As Variant, arr2 As Variant, i As Long, code As String

code = "A**" 'For any order codes containing A11, A12, A13, A21, A22, _
A23, etc

Lastrow = Sheets("SO Hits Data Single Row").Cells(Rows.Count, _
1).End(xlUp).Row

arr1 = Sheets("SO Hits Data Single Row").Range("B4:B" & Lastrow).Value
arr2 = Filter(arr1, code)
Sheets("A##").Range("a1") = arr2

Но это просто выдает ошибку несоответствия.

Ниже приведен пример результата, которого мне нужно достичь.

Как присвоить значения из массива из 2 столбцов массиву из одного столбца на основе столбца, соответствующего определенным критериям

Не могли бы вы подробнее рассказать об этом 9 of 45 codes (поделитесь списком) и об этом Set Sht = wb.Worksheets("A##"): Code = "A**"? Также опишите, на каких листах это происходит, что делает формула VLOOKUP и т. д. Будьте более конкретными. Вы можете отредактируй свой пост в любое время.

VBasic2008 21.03.2022 23:43

Хорошо, я отредактировал это для уточнения.

CodeNewbie 21.03.2022 23:50

Какая часть кода требует времени и зависает? Вы установили Application.Calculation на xlCalculationManual, чтобы приостановить вычисления во время выполнения кода? Кроме того, есть ли какие-либо данные между выходными номерами деталей или это «значение1, 7 пустых строк, значение2, 7 пустых строк…»? Изображение вывода поможет сделать это минимальный воспроизводимый пример

Chronocidal 22.03.2022 00:01

Извините, все еще не понял. Вы опубликовали этот снимок экрана с 13 парами значений. Не могли бы вы поделиться названием рабочего листа, именами рабочих листов и адресами ячеек, куда нужно скопировать эти 13 пар значений?

VBasic2008 22.03.2022 00:02

Пытаясь упростить ситуацию, я сделал это неясным. Мои извинения. Я обновил результат, которого пытаюсь достичь. Я справился с этим с помощью очень грубого макроса, который зависает, когда я собираю номера деталей для каждого листа (лист «A##», показанный на последнем скриншоте, который я добавил). Есть 5 листов: «A##», «B##», «C#1», «C#2», «C#3», которые берут номера деталей из «SO Hits Data Single Row». Что касается того, какие коды помещаются на какой лист, символы «#» в имени листа в основном являются подстановочными знаками и могут быть любым числом с постоянными другими цифрами (например, «C# 3» должен захватывать коды C13, C23 и C33.

CodeNewbie 22.03.2022 15:11
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
3
5
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если у вас Excel 2019 или Excel 365, вы можете использовать встроенные функции SORT и FILTER, чтобы значительно упростить задачу:

Public Function PartsToSheet(OrderPrefix AS String) AS Boolean
    PartsToSheet = False
    On Error GoTo FuncErr 'Return False if there is an error
    Dim calcTMP As xlCalculation
    calcTMP = Application.Calculation
    'Only Calculate Formulae when we explicitly say to
    Application.Calculation = xlCalculationManual
    
    Dim wsSource AS Worksheet, wsDestination AS Worksheet
    Dim lParts AS Long, lRecords AS Long
    Dim adTable AS String, adOrders AS String
    
    Set wsSource = ThisWorkbook.Worksheets("SO Hits Data Single Row")
    Set wsDestination = ThisWorkbook.Worksheets(OrderPrefix & "##")
    
    'Prepare the Destination
    With wsDestination
        'Deleting Rows & Columns frees up the Used Range, freeing more memory than Clear does
        .Range(.Cells(1, 1), .Range(.Rows.Count, 1)).EntireRow.Delete
        .Range(.Cells(1, 1), .Range(1, .Columns.Count)).EntireColumn.Delete
    End With
    
    lParts = Application.CountA(wsSource.Columns(1))
    lRecords = Application.CountIf(wsSource.Columns(2), OrderPrefix & "*")
    
    adTable = wsSource.Range(wsSource.Cells(1, 1),wsSource.Cells(lParts, 2)).Address(True, True, xlA1, True)
    adOrders = wsSource.Range(wsSource.Cells(1, 2),wsSource.Cells(lParts, 2)).Address(True, True, xlA1, True)
    
    If lRecords > 0 Then 'If there are Order Codes for this Sheet
        wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Formula = _
            "=IF(MOD(ROW()+6,8)>0, """", INDEX(SORT(" & _
                "FILTER(" & adTable & ", LEFT(" & adOrders & ", 1) = """ & OrderPrefix & """)" & _
                ", 2), (ROW()+6)/8, 1))"
        
        wsDestination.Columns(1).Calculate 'Explicitly calculate formulae
        
        wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Value = _
            wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Value
    End If
    
    PartsToSheet = True 'Success!
FuncErr:
    On Error GoTo -1 'Clear any errors in the handler
    Application.Calculation = calcTMP
End Function

По сути, мы заполняем первый столбец целевого листа функцией, которая будет пустой для 7 строк (IF(MOD(ROW()+6,8)>0,), затем предоставляем следующую запись (INDEX(.., (ROW()+6)/8, 1)) в массиве, который мы получаем, FILTER для префикса и SORT для код заказа.

Затем мы «выравниваем» результат, преобразуя его из динамических формул в статические значения.

В Excel 2019 нет SORT и FILTER.

VBasic2008 22.03.2022 00:39

Я пытался использовать это с входами «A», «A##» и «A**», но я не получил ничего, кроме FALSE. Также, чтобы уточнить, это Excel 365.

CodeNewbie 22.03.2022 15:37
Ответ принят как подходящий

Итак, я обнаружил, что массив на самом деле был лучшим способом приблизиться к этому. Тем не менее, размер файла явно был серьезной проблемой, и я обнаружил, что это связано с тем, что пустые ячейки были включены в текущий выбор. Однажды я исправил, что макрос работал быстрее, но все равно занимал слишком много времени. В итоге я написал код, чтобы сохранить данные в массив, а затем отфильтровать их позже, как показано ниже.

Sub Example()

Dim arr1 As Variant, arr2(10000) As Variant, i As Long, j As Long, k As Long, Filter As String

Application.ScreenUpdating = False 'Freeze screen while macro runs
Application.EnableEvents = False 'Disable popups
Application.Calculation = xlManual 'Disable Sheet calcs

Filter = "A**"
arr1 = ActiveWorkbook.Worksheets("Sheet1").Range("A4:B12000").Value
j= Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A20000"))
    For i = 1 To j
        If arr1(i, 2) Like Filter Then
            arr2(k) = arr1(i, 1)
            arr2(k + 1) = ""
            arr2(k + 2) = ""
            arr2(k + 3) = ""
            arr2(k + 4) = ""
            arr2(k + 5) = ""
            arr2(k + 6) = ""
            arr2(k + 7) = ""
            k = k + 8 'This was so I could adjust for the blank spaces I needed between each value in the array
        End If
    Next i

Application.ScreenUpdating = True 'Unfreeze screen
Application.Calculation = xlAutomatic 'Enable Sheet calcs
Application.EnableEvents = True 'Enable popups

End Sub

Приведенный выше код немного более специфичен для моей ситуации, но ниже приведена более общая форма для будущих зрителей.

Sub Example()

Dim arr1 As Variant, arr2(10000) As Variant, i As Long, j As Long, k As Long, Filter As String

Application.ScreenUpdating = False 'Freeze screen while macro runs
Application.EnableEvents = False 'Disable popups
Application.Calculation = xlManual 'Disable Sheet calcs

Filter = "A**" 'This is where you would put your filter instead of "A**"
arr1 = ActiveWorkbook.Worksheets("Sheet1").Range("A4:B12000").Value
j= Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A20000"))
    For i = 1 To j
        If arr1(i, 2) Like Filter Then
            arr2(k) = arr1(i, 1)
        End If
    Next i

Application.ScreenUpdating = True 'Unfreeze screen
Application.Calculation = xlAutomatic 'Enable Sheet calcs
Application.EnableEvents = True 'Enable popups

End Sub

Другие вопросы по теме