Чтение большого текстового файла останавливает работу Excel, это можно сделать с помощью Python? - PullRequest
0 голосов
/ 08 мая 2019

Я импортирую данные через код импорта SQL через Excel VBA в sheet6. Затем я перебираю каждую строку импортированных данных в поисках соответствия на основе двух критериев. Столбец A и значение столбца G присутствуют в строке из большого текстового файла с 2 миллионами строк данных. Если совпадение найдено, то первое значение из текстового файла с разделителями-запятыми добавляется в столбец E.

Я пытался исследовать коды Excel VBA, но он действительно медленный при циклическом просмотре 5000 строк данных. Это может занять до 30 минут, чтобы бежать. Надеясь, что, возможно, решение Python может быть ответом.

Const strFileName = "T:\Hex\ModeS-Mil.txt"

Sub FillMTextFile()
Dim StartTime As Double
Dim SecondsElapsed As Double
Dim strsearch As String
Dim MReg As String
Dim MType As String
Dim strLine As String
Dim f As Integer
Dim lngLine As Long
Dim blnFound As Boolean
Dim x As Long
Dim lrow As Long
lrow = Sheet6.Range("A" & Rows.Count).End(xlUp).Row
StartTime = Timer
For x = 2 To 3000
MReg = Sheet6.Range("A" & x).Value
MType = Sheet6.Range("G" & x).Value
strsearch = MReg & "," & MType
f = FreeFile
Open strFileName For Input As #f
Do While Not EOF(f)
lngLine = lngLine + 1
Line Input #f, strLine
If InStr(1, strLine, strsearch, vbBinaryCompare) > 0 Then
Sheet6.Range("E" & x).Value = UCase(Split(strLine, ",")(0))
On Error GoTo err
blnFound = True
Exit Do
End If
Loop
Close #f
If Not blnFound Then
End If
err:
Next x
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "code time " & SecondsElapsed & " seconds", vbInformation
End Sub

1 Ответ

0 голосов
/ 10 мая 2019

Работает ли этот подход, зависит от того, выполняются ли все следующие условия:

  • Вы можете заранее указать, какие столбцы в CSV соответствуют столбцам A и G из Sheet6
  • Вы можете заранее указать, какой столбец вашего CSV вы хотите записать в столбец E из Sheet6
  • Ни одно из самих значений в вашем CSV не содержит символов новой строки или запятых.

Я надеюсь, что все имеет смысл. ^

Option Explicit

Const CSV_FILE_PATH As String = "T:\Hex\ModeS-Mil.txt"
Const FIRST_ROW_CONTAINING_DATA As Long = 2 ' Exclude header row'

Sub FillMTextFile()

    Dim csvMap As Collection
    Set csvMap = GetMapForCsv()

    Dim lastRow As Long
    lastRow = Sheet6.Cells(Sheet6.Rows.Count, "A").End(xlUp).Row
    Debug.Assert lastRow >= FIRST_ROW_CONTAINING_DATA

    Dim substringsInColumnA() As Variant ' This needs a better name, especially if your columns change.'
    substringsInColumnA = Sheet6.Range("A" & FIRST_ROW_CONTAINING_DATA, "A" & lastRow).Value

    Dim substringsInColumnG() As Variant ' This needs a better name, especially if your columns change.'
    substringsInColumnG = Sheet6.Range("G" & FIRST_ROW_CONTAINING_DATA, "G" & lastRow).Value

    Dim outputRowCount As Long
    outputRowCount = lastRow - FIRST_ROW_CONTAINING_DATA + 1

    Dim arrayToWriteToSheet() As String
    ReDim arrayToWriteToSheet(1 To outputRowCount, 1 To 1)
    Debug.Assert LBound(arrayToWriteToSheet, 1) = LBound(substringsInColumnA, 1)

    Dim rowIndex As Long
    For rowIndex = LBound(substringsInColumnA, 1) To UBound(substringsInColumnA, 1)

        Dim substringsToJoin(0 To 1) As String ' Static, re-use every iteration'
        substringsToJoin(0) = UCase$(CStr(substringsInColumnA(rowIndex, 1)))
        substringsToJoin(1) = UCase$(CStr(substringsInColumnG(rowIndex, 1)))

        Dim substringToSearchFor As String
        substringToSearchFor = Join$(substringsToJoin, ",")

        arrayToWriteToSheet(rowIndex, 1) = _
            GetCollectionItemOrDefault(someCollection:=csvMap, someKey:=substringToSearchFor)

    Next rowIndex

    Sheet6.Cells(FIRST_ROW_CONTAINING_DATA, "E").Resize(UBound(arrayToWriteToSheet, 1), UBound(arrayToWriteToSheet, 2)) = arrayToWriteToSheet
End Sub

Private Function GetMapForCsv() As Collection
    ' This function tries to take advantage of which columns '
    ' in the CSV to look at -- and builds a collection in which: '
    '   • each collection key = the concatenation of columns that need to contain matches '
    '   • each collection item = the value that we later want to look up '

    Const CSV_COLUMN_TO_WRITE_TO_COLUMN_E As Long = 1 ' First column'
    Const CSV_COLUMN_CORRESPONDING_TO_COLUMN_A As Long = 2 ' Second column'
    Const CSV_COLUMN_CORRESPONDING_TO_COLUMN_G As Long = 3 ' Third column'

    Dim outputCollection As Collection
    Set outputCollection = New Collection

    Dim fileHandle As Long
    fileHandle = FreeFile

    Open CSV_FILE_PATH For Input As #fileHandle ' Open once outside of loop.'
    Do Until EOF(fileHandle)

        ' File is probably too large to fit in memory, hence iterating line by line'
        ' but I think this assumes there are no escaped/quoted new line characters in the values themselves'
        Dim currentLineInCsv As String
        Line Input #fileHandle, currentLineInCsv
        currentLineInCsv = UCase$(currentLineInCsv)

        Dim currentValues() As String
        currentValues = Split(currentLineInCsv, ",", -1, vbBinaryCompare)

        Dim toJoin(0 To 1) As String
        toJoin(0) = currentValues(CSV_COLUMN_CORRESPONDING_TO_COLUMN_A - 1) ' 0-based array assumed'
        toJoin(1) = currentValues(CSV_COLUMN_CORRESPONDING_TO_COLUMN_G - 1) ' 0-based array assumed'

        Dim collectionKey As String
        collectionKey = Join$(toJoin, ",")

        Dim collectionItem As String
        collectionItem = currentValues(CSV_COLUMN_TO_WRITE_TO_COLUMN_E - 1) ' 0-based array assumed'

        On Error Resume Next
        outputCollection.Add Item:=collectionItem, key:=collectionKey
        On Error GoTo 0
    Loop
    Close #fileHandle

    Set GetMapForCsv = outputCollection
End Function

Private Function GetCollectionItemOrDefault(ByVal someCollection As Collection, ByVal someKey As String, Optional ByVal defaultValue As String = vbNullString) As String
    ' Returns the item corresponding to some key of a collection'
    ' If the key does not exist, returns the default.'

    Dim keyDoesNotExist As Boolean
    Dim itemToReturn As String

    On Error Resume Next
    itemToReturn = someCollection(someKey)
    keyDoesNotExist = (Err.Number <> 0)
    On Error GoTo 0

    If keyDoesNotExist Then itemToReturn = defaultValue
    GetCollectionItemOrDefault = itemToReturn
End Function

Что касается реализации, вам нужно изменить эти строки (в коде для функции GetMapForCsv):

Const CSV_COLUMN_TO_WRITE_TO_COLUMN_E As Long = 1 ' First column'
Const CSV_COLUMN_CORRESPONDING_TO_COLUMN_A As Long = 2 ' Second column'
Const CSV_COLUMN_CORRESPONDING_TO_COLUMN_G As Long = 3 ' Third column'

так, чтобы назначения были верны для вашего CSV. В настоящее время вышеуказанные задания предполагают, что:

  • второй столбец в вашем CSV соответствует значениям в Sheet6 столбце A
  • третий столбец в вашем CSV соответствует значениям в Sheet6 столбце G
  • вы хотите посмотреть значения в первом столбце вашего CSV и записать их в столбец Sheet6 E.

Мой CSV содержал только 3 столбца, мой Sheet6 содержал 10 тыс. Значений для поиска; и на моей машине код выше взял:

  • ~ 195,6 секунд для 10 миллионов строк в CSV
  • ~ 35,2 секунды для 2 миллионов строк в CSV

Альтернативные подходы могут включать:

  • Использование Python. Должно быть возможным использовать выражение генератора для циклического перебора 2 миллионов строк файла CSV с эффективным использованием памяти. Я думаю, что встроенный модуль csv может справиться с этой задачей, но вам также может понадобиться модуль / библиотека Python для взаимодействия с Excel, например openpyxl, XlsxWriter (чтобы вы могли прочитать значения в столбцах A и G). Код Python, вероятно, будет намного короче / проще в обслуживании.

  • Использование Power Query (который уже несколько лет является частью Excel в Windows). Вы можете прочитать столбцы A и G в одну таблицу, а CSV - в другую таблицу. Вывод, вероятно, будет загружен в виде таблицы на лист. Не уверен, насколько это будет эффективно / эффективно, но, возможно, стоит настроить данные таким образом, чтобы вы могли использовать Table.Merge. В противном случае, возможно, вы могли бы вернуться к Text.Contains или что-то еще.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...