Алгоритм сортировки данных 900000 строк данных

У меня есть четыре столбца AA, AB, AC и AD со значениями, которые я хочу отсортировать по определенному шаблону:

Столбец АА Столбец AB Колонка АС Столбец AD Столбец АЕ Ряд 1 123 444 6666 Ряд 2 А тс s4 23 Ряд 3 1111 56 хх 23 Ряд 4 Д 56 Ф 4 Ряд 5 56 Ф А Ряд 6 456 55 3333 23 Ряд 7 А 333 А56 55555 Ряд 8 1 555 ВБК А Ряд 9 А 5899 Б6 23 Ряд 10 2 ТЗУ 98 56
  1. Все чисто числовые значения должны быть перемещены вперед перед всеми другими буквенно-цифровыми значениями.

  2. Пустые поля необходимо поставить в конец.

  3. Числовое значение с наибольшим количеством символов должно быть помещено в первый столбец. Например, значение «55555» в строке 7 и столбце AD должно быть после выполнения макроса в строке 7 и столбце AA.

Конечный результат приведенного выше примера должен выглядеть так:

Столбец АА Столбец AB Колонка АС Столбец AD Столбец АЕ Ряд 1 6666 123 444 Ряд 2 23 А тс s4 Ряд 3 1111 56 23 хх Ряд 4 56 4 Д Ф Ряд 5 56 Ф А Ряд 6 3333 456 55 23 Ряд 7 55555 333 А А56 Ряд 8 555 1 ВБК А Ряд 9 5899 23 А Б6 Ряд 10 98 56 2 ТЗУ

Мое решение ниже. Проблема в том, что я не смог реализовать пункт 2, переместив пустое поле в конец. Кроме того, я использую циклы и хочу преобразовать 900 000 (!) строк данных. Запуск такого макроса занимает несколько дней... любое другое решение приветствуется. Спасибо.

Option Explicit

Sub resort()

    Dim i As Long
    Dim j As Long
    Dim temp As Range
    
    With Worksheets("Tabelle1")
    
    For j = 1 To 10
    
        For i = 2 To 15
        
            If IsNumeric(.Range("AA" & i)) = False And IsNumeric(.Range("AB" & i)) = True Then
            
                .Range("AB" & i).Copy Destination:=.Range("AE" & i)
                .Range("AA" & i).Copy Destination:=.Range("AB" & i)
                .Range("AE" & i).Copy Destination:=.Range("AA" & i)
                .Range("AE" & i).Clear
                            
            End If
        
        Next i
        
        For i = 2 To 15
        
            If IsNumeric(.Range("AB" & i)) = False And IsNumeric(.Range("AC" & i)) = True Then
            
                .Range("AC" & i).Copy Destination:=.Range("AE" & i)
                .Range("AB" & i).Copy Destination:=.Range("AC" & i)
                .Range("AE" & i).Copy Destination:=.Range("AB" & i)
                .Range("AE" & i).Clear
                            
            End If
        
        Next i
        
        For i = 2 To 15
        
            If IsNumeric(.Range("AC" & i)) = False And IsNumeric(.Range("AD" & i)) = True Then
            
                .Range("AD" & i).Copy Destination:=.Range("AE" & i)
                .Range("AC" & i).Copy Destination:=.Range("AD" & i)
                .Range("AE" & i).Copy Destination:=.Range("AC" & i)
                .Range("AE" & i).Clear
                            
            End If
        
        Next i
        
     Next j
        
        
     '++++++++++++++++++++++++++++++++++++++++++++++++++++'
        
     For j = 1 To 10
        
        For i = 2 To 15
        
            If IsNumeric(.Range("AB" & i)) = True Then
            
                If Len(.Range("AB" & i)) > Len(.Range("AA" & i)) Then
            
                    .Range("AB" & i).Copy Destination:=.Range("AE" & i)
                    .Range("AA" & i).Copy Destination:=.Range("AB" & i)
                    .Range("AE" & i).Copy Destination:=.Range("AA" & i)
                    .Range("AE" & i).Clear
                    
                End If
                            
            End If
        
        Next i
    
        For i = 2 To 15
        
            If IsNumeric(.Range("AC" & i)) = True Then
            
                If Len(.Range("AC" & i)) > Len(.Range("AB" & i)) Then
                
                    .Range("AC" & i).Copy Destination:=.Range("AE" & i)
                    .Range("AB" & i).Copy Destination:=.Range("AC" & i)
                    .Range("AE" & i).Copy Destination:=.Range("AB" & i)
                    .Range("AE" & i).Clear
                                
                End If
                
            End If
        
        Next i
      
        For i = 2 To 15

            If IsNumeric(.Range("AD" & i)) = True Then

                If Len(.Range("AD" & i)) > Len(.Range("AC" & i)) Then

                    .Range("AD" & i).Copy Destination:=.Range("AE" & i)
                    .Range("AC" & i).Copy Destination:=.Range("AD" & i)
                    .Range("AE" & i).Copy Destination:=.Range("AC" & i)
                    .Range("AE" & i).Clear

                End If

            End If

        Next i
    
    Next j
        
    End With

End Sub

Пожалуйста, также включите образцы данных в редактируемом формате, чтобы нам было легче протестировать предлагаемое решение. (вы можете отформатировать необработанную дату здесь: tablesgenerator.com/markdown_tables )

Máté Juhász 24.07.2024 08:45

Готово, спасибо за ссылку.

user26495834 24.07.2024 08:53

Если у вас есть две буквенно-цифровые ячейки, есть ли какое-нибудь правило их сортировки? В вашем примере выглядит так, как будто эти значения сохраняют порядок (см., например, строку 2). Это важно или их можно отсортировать, например, по алфавиту?

FunThomas 24.07.2024 09:31

Я правильно вас понял, вы не меняете порядок строк, вы сортируете только по столбцам, верно?

Storax 24.07.2024 09:54

Неважно, как отсортированы буквенно-цифровые ячейки. Только числовые ячейки должны быть в правильном порядке. Порядок строк не изменяется.

user26495834 24.07.2024 10:07

В принципе, вы можете использовать одну большую сортировку, эквивалент vba =LET(d,TOCOL(IF(A1:D10 = "","",A1:D10)),WRAPROWS(SORTBY(d,QUOT‌​IENT(SEQUENCE( ROWS(d‌​),,0),4),1,ISNUMBER(‌​d),-1,LEN(d),-1,d,1)‌​,4)) но некоторые из этих функций имеют ограничение в 2 ^ 20 строк, поэтому вы не сможете создавать и манипулировать вектором размером 900 000 X 4 строк.

Tom Sharpe 24.07.2024 15:27
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
3
6
94
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вам предстоит решить 3 задачи:

а) У вас огромный объем данных, поэтому вам нужна быстрая процедура.
Это просто: считайте все данные в память за один раз (в двумерный массив). Работайте над этим массивом. Когда все будет отсортировано, запишите данные обратно в Excel за один раз.

Sub sortMydata()
    With Worksheets("Tabelle1")
        Dim rowcount As Long
        rowcount = .Range("AA1").CurrentRegion.Rows.Count
        ' Read Excel data into 2-dimensional array
        Dim data
        data = .Range("AA1").Resize(rowcount, 4)
        ' Sort all rows
        For row = 2 To rowcount
            sortrow data, row
        Next
        ' Write sorted data back into sheet    
        .Range("AA1").Resize(rowcount, 4) = data
    End With
End Sub

б) Вам необходимо отсортировать данные (строка за строкой). Для этого нам нужен алгоритм сортировки. Поскольку мы всегда сортируем лишь очень небольшое количество значений (по 4 в строке), лучшим вариантом является простая пузырьковая сортировка. В Интернете можно найти множество реализаций. Единственное, что нам нужно знать, это то, что мы хотим сортировать значения одной строки, тогда как большинство алгоритмов предполагают, что вы хотите сортировать данные по одному (или нескольким) столбцам.

Sub sortrow(data, row As Long)
    Dim i As Long, j As Long
    ' A simple Bubble Sort to sort the values of one Row
    For i = LBound(data, 2) To UBound(data, 2) - 1
        For j = i To UBound(data, 2)
            If sortBefore(data(row, j), data(row, i)) Then
                Dim tmp As Variant
                tmp = data(row, i)
                data(row, i) = data(row, j)
                data(row, j) = tmp
            End If
        Next
    Next
End Sub

в) Вам нужен алгоритм, который сравнивает два значения, чтобы решить, какое из них будет «первым». Алгоритм сортировки будет использовать это для сортировки ваших данных.

Function sortBefore(v1 As Variant, v2 As Variant) As Boolean
    If v1 = "" Then
        sortBefore = False            ' Blanks to the end
    ElseIf IsNumeric(v1) Then
        If IsNumeric(v2) Then
            sortBefore = v1 > v2      ' Compare Numeric values: Larger first
        Else
            sortBefore = True         ' Number before string
        End If
    Else
        If IsNumeric(v2) Then
            sortBefore = False        ' String after numeric
        Else
            sortBefore = LCase(v1) < LCase(v2)      ' Compare string values: Smaller first
        End If
    End If
End Function

Запуск программы со 100 000 строк данных занял 1–2 секунды.

Другим подходом может быть использование Power Query, также этот подход должен быстро работать с большими данными.

Код для использования:

let
    Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content],
    #"Unpivoted Other Columns" = Table.UnpivotOtherColumns(Source, {"Column AA"}, "column name", "Value"),
    #"Removed Columns" = Table.RemoveColumns(#"Unpivoted Other Columns",{"column name"}),
    #"Added Custom" = Table.AddColumn(#"Removed Columns", "sort rank", each try Text.Length(Number.ToText([Value])) otherwise 0),
    #"Grouped Rows" = Table.Group(#"Added Custom", {"Column AA"}, {{"row", each _}}),
    sorted = Table.TransformColumns(#"Grouped Rows", {"row", each Table.Sort(_, {"sort rank", Order.Descending})}),
    index_added = Table.TransformColumns(sorted, {"row", each Table.AddIndexColumn(_, "index")}),
    #"Expanded row" = Table.ExpandTableColumn(index_added, "row", {"Value", "index"}, {"Value", "index"}),
    #"Pivoted Column" = Table.Pivot(Table.TransformColumnTypes(#"Expanded row", {{"index", type text}}, "en-GB"), List.Distinct(Table.TransformColumnTypes(#"Expanded row", {{"index", type text}}, "en-GB")[index]), "index", "Value")
in
    #"Pivoted Column"

Вывод: (слева: отсортировано, справа: оригинал)

Я использовал ArrayList, но не уверен, что это применимо к вам.

Option Explicit

Sub SortIt()
    Dim list As Object, listA As Object
    Set list = CreateObject("System.Collections.ArrayList")
    Set listA = CreateObject("System.Collections.ArrayList")

    'Set list = New ArrayList
    'Set listA = New ArrayList

    Dim v As Variant
    Dim i As Long
    Dim sngRow As Range
    Dim wks As Worksheet
    Set wks = ActiveSheet
    
    For Each sngRow In wks.Range("A1:D10").Rows
        v = sngRow.Value
        For i = LBound(v, 2) To UBound(v, 2)
            If Len(v(1, i)) > 0 Then
                If IsNumeric(v(1, i)) Then
                    list.Add v(1, i)
                Else
                    listA.Add v(1, i)
                End If
            End If
        Next
        list.Sort
        list.Reverse
        Debug.Print list.Count, listA.Count
    
        ' write to sheet to the right in column 6
        
        v = list.toarray
        With wks
            .Range(.Cells(sngRow.Row, 6), .Cells(sngRow.Row, 6 + list.Count - 1)) = v
            v = listA.toarray
            .Range(.Cells(sngRow.Row, 6 + list.Count), .Cells(sngRow.Row, 6 + list.Count + listA.Count - 1)) = v
        End With
        list.Clear
        listA.Clear
    Next sngRow
End Sub

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

Изменил его таким образом, чтобы количество обращений к листу было сведено к минимуму.

Option Explicit

Sub sortMemory()
    Dim list As Object, listA As Object
    Set list = CreateObject("System.Collections.ArrayList")
    Set listA = CreateObject("System.Collections.ArrayList")

    'Set list = New ArrayList
    'Set listA = New ArrayList

    Dim v As Variant
    Dim vDat As Variant
    Dim i As Long, j As Long, k As Long, listCount As Long
    
    Dim wks As Worksheet
    Set wks = ActiveSheet
        
    ' Data in Region of A1
    vDat = wks.Range("A1").CurrentRegion.Value
    
    For j = LBound(vDat, 1) To UBound(vDat, 1)
        For i = LBound(vDat, 2) To UBound(vDat, 2)
            If Len(vDat(j, i)) > 0 Then
                If IsNumeric(vDat(j, i)) Then
                    list.Add vDat(j, i)
                Else
                    listA.Add vDat(j, i)
                End If
            End If
        Next
        list.Sort
        list.Reverse
            
        ' Change array
        v = list.toarray
        For k = LBound(v) To UBound(v)
            vDat(j, k + 1) = v(k)
        Next k
        
        v = listA.toarray
        listCount = list.Count
        
        For k = 1 + listCount To UBound(vDat, 2)
            vDat(j, k) = ""
        Next
        If UBound(v) >= 0 Then
            For k = LBound(v) To UBound(v)
                vDat(j, k + 1 + listCount) = v(k)
            Next k
        End If
        
        list.Clear
        listA.Clear
    Next
    
    ' Output starting in Column 6, adjust accordingly
    With wks
        .Range(.Cells(1, 6), .Cells(UBound(vDat, 1), 6 + UBound(vDat, 2) - 1)) = vDat
    End With

End Sub

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