Что нового

Подскажите как разобрать файл на массивы?

mike2003

Новичок
Сообщения
10
Репутация
0
Есть файл данных вида:

метка1
данные11111
данные2222
данные333

метка2
еще данные11111
еще данные2222
еще данные333

метка3
....и так далее


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

Dim $a[0], $b[0]
$t = FileReadLine
if $t = метка1 then arr = $a
if $t = метка2 then arr = $b
_ArrayAdd(arr, $t)


Можно для _ArrayAdd подменить целевой массив на-лету как в с++?
 

Webarion

Осваивающий
Сообщения
143
Репутация
24
Не совсем понятно, что вы хотите. Каким должен быть итоговый массив? Один из способов для FileReadLine:
Код:
#include <Array.au3>

$hFile = FileOpen(@ScriptDir & '\text.txt', 0) ; файл
If $hFile = -1 Then
    MsgBox(4096, "Ошибка", "Невозможно открыть файл.")
    Exit
EndIf

Local $sTags = "метка1метка2метка3" ; отслеживаемые метки
Local $aTags[0], $aParams[0], $aTagTitle[0], $iTrig = 0
Local $sLine, $sLineStripWS
While 1
    $sLine = FileReadLine($hFile)
    If @error = -1 Then ExitLoop
    $sLineStripWS = StringStripWS($sLine, 3)
    If Not $sLineStripWS Then ContinueLoop
    If StringInStr($sTags, $sLineStripWS) Then
        _AddDataToArray($aTagTitle, $sLineStripWS)
        If $iTrig Then
            _AddDataToArray($aTags, $aParams)
            ReDim $aParams[0]
        EndIf
        $iTrig = 1
        ContinueLoop
    Else
        _AddDataToArray($aParams, $sLine)
    EndIf
WEnd
_AddDataToArray($aTags, $aParams)
FileClose($hFile)

; Просматриваем добавленные массивы внутри массива $aTags
For $i = 0 To UBound($aTags) - 1
    _ArrayDisplay($aTags[$i], $aTagTitle[$i])
Next

; добавляем в массив с увеличением его размера
Func _AddDataToArray(ByRef $aArray, $vDava)
    Local $iUB = UBound($aArray)
    ReDim $aArray[$iUB + 1]
    $aArray[$iUB] = $vDava
EndFunc

На лету:
Код:
#include <Constants.au3>
#include <Array.au3>

$hFile = FileOpen(@ScriptDir & '\text.txt', 0) ; файл
If $hFile = -1 Then
    MsgBox(4096, "Ошибка", "Невозможно открыть файл.")
    Exit
EndIf

Local $aTagNames = ["метка1", "метка2", "метка3"] ; отслеживаемые метки
Local $sLine


Local $aArrayResult[0], $aArr[0]
While 1

    $sLine = FileReadLine($hFile)
    If @error = -1 Then ExitLoop
    If Not $sLine Then ContinueLoop

    If $sLine = "метка1" Then
        _GetNextParams($hFile, $aArr, $aTagNames)
        _ArrayAdd($aArrayResult, $sLine)
         _ArrayAdd($aArrayResult, $aArr)
    EndIf
    If $sLine = "метка2" Then
        _GetNextParams($hFile, $aArr, $aTagNames)
        _ArrayAdd($aArrayResult, $sLine)
         _ArrayAdd($aArrayResult, $aArr)
    EndIf

WEnd

_ArrayDisplay($aArrayResult)

FileClose($hFile)

; #ФУНКЦИЯ# =====================================================================================================================
; Описание ....: поиск следующих параметров
; Параметры ...: $hFile         - дескриптор файла
;                $aSearchParams - массив, который будет заполнен найденными строками
;                $aTagNames     - Массив со всеми метками
; ===============================================================================================================================
Func _GetNextParams($hFile, ByRef $aSearchParams, $aTagNames)
    ReDim $aSearchParams[0]
    Local $sLine, $iPos
    While 1
        $iPos = FileGetPos ($hFile)
        $sLine = FileReadLine($hFile)
        If @error = -1 Then ExitLoop
        If Not $sLine Then ContinueLoop
        For $sTN In $aTagNames
            If $sTN = StringStripWS($sLine, 3) Then
                ExitLoop (2)
            EndIf
        Next
        _ArrayAdd($aSearchParams, $sLine)
    WEnd
    FileSetPos($hFile, $iPos, $FILE_BEGIN)
    Return $aSearchParams
EndFunc
 
Последнее редактирование:
Автор
M

mike2003

Новичок
Сообщения
10
Репутация
0
Ну все элементы данных должны попасть в свой массив. А метка говорит в какой именно.
Встретил метку - заполняй другой массив. Я же приложил шаблон работы. Вот пример кода, который рабоатет но хочется оптимизировать, там много строк еще.
Код:
$sFileRead = FileReadLine($hFileOpen)
ElseIf ($sFileRead = "mark1") Then
    $ArrMode = 1
ElseIf ($sFileRead = "mark2") Then
    $ArrMode = 2
ElseIf ($sFileRead = "mark3) Then
    $ArrMode = 3
EndIf

Switch $ArrMode
    Case 1
        _ArrayAdd($Arr1, $sFileRead)
    Case 2
        _ArrayAdd($Arr2, $sFileRead)
    Case 3
        _ArrayAdd($Arr3, $sFileRead)
EndSwitch
 
Последнее редактирование:

Webarion

Осваивающий
Сообщения
143
Репутация
24
Ну все элементы данных должны попасть в свой массив. А метка говорит в какой именно.
Это есть, в моём первом примере. Массивы извлекаются из массива $aTags, по индексу.
Этот вариант делает тоже самое:
Код:
#include <Constants.au3>
#include <Array.au3>

$hFile = FileOpen(@ScriptDir & '\text.txt', 0) ; файл
If $hFile = -1 Then
    MsgBox(4096, "Ошибка", "Невозможно открыть файл.")
    Exit
EndIf

Local $aTagNames = ["метка1", "метка2", "метка3"] ; отслеживаемые метки
Local $sLine


Local $aArr1[0], $aArr2[0]
While 1

    $sLine = FileReadLine($hFile)
    If @error = -1 Then ExitLoop
    If Not $sLine Then ContinueLoop

    If $sLine = "метка1" Then _GetNextParams($hFile, $aArr1, $aTagNames)
    If $sLine = "метка2" Then _GetNextParams($hFile, $aArr2, $aTagNames)

WEnd

_ArrayDisplay($aArr1)
_ArrayDisplay($aArr2)

FileClose($hFile)

; #ФУНКЦИЯ# =====================================================================================================================
; Описание ....: поиск следующих параметров
; Параметры ...: $hFile         - дескриптор файла
;                $aSearchParams - массив, который будет заполнен найденными строками
;                $aTagNames     - Массив со всеми метками
; ===============================================================================================================================
Func _GetNextParams($hFile, ByRef $aSearchParams, $aTagNames)
    ReDim $aSearchParams[0]
    Local $sLine, $iPos
    While 1
        $iPos = FileGetPos($hFile)
        $sLine = FileReadLine($hFile)
        If @error = -1 Then ExitLoop
        If Not $sLine Then ContinueLoop
        For $sTN In $aTagNames
            If $sTN = StringStripWS($sLine, 3) Then
                ExitLoop (2)
            EndIf
        Next
        _ArrayAdd($aSearchParams, $sLine)
    WEnd
    FileSetPos($hFile, $iPos, $FILE_BEGIN)
    Return $aSearchParams
EndFunc
 
Последнее редактирование:
Автор
M

mike2003

Новичок
Сообщения
10
Репутация
0
Последний вариант вроде норм. Можно кстати метки на забивать, они же есть в массиве.
Код:
$i = 0
If $sLine = $aTagNames[$i] Then _GetNextParams($hFile, $aArr1, $aTagNames)
$i +=1

Но есть пара вопросов по поводу функции:
1. Не пойму никак зачем двигать FileSetPos? Вроде же по кольцу получается ходим в цикле. Запомнили, считали, восставовили и никогда не выйдем.
2. Зачем Return $aSearchParams ведь мы работаем через ByRef
 

Norm

Продвинутый
Сообщения
269
Репутация
70
На мой взгляд так было бы быстрее, во всех отношениях
Код:
#include <Array.au3>

$sMark = "Marc-"
$sStrg = "Daten-"
$sComplStrg = ""
For $nN = 0 To 9
    $sComplStrg &= $sMark & $nN & @CRLF
    For $nJ = 0 To 5
        $sComplStrg &= "Row-"& $nJ &"  "& $sStrg & $nN &"/"& $nJ &  $nN &"/"& $nJ &  $nN &"/"& $nJ & @CRLF
    Next
Next
FileDelete(@ScriptDir &"\Test.txt")
FileWrite(@ScriptDir &"\Test.txt", $sComplStrg)
MsgBox(0,"String",$sComplStrg)

; START
; STEP 1 =========================================================================================================
$aArray = StringRegExp(FileRead(@ScriptDir &"\Test.txt"), "("& $sMark &"\d+)\R((?s).+?)(?="& $sMark &"\d+|\Z)", 3)
_ArrayDisplay($aArray, "Array 1D")

; STEP 2 ======================
$nUB = UBound($aArray)
Dim $nT = 0, $aArray2D[$nUB/2][2]
For $nN = 0 To $nUB-1 Step 2
    $aArray2D[$nT][0] = $aArray[$nN]
    $aArray2D[$nT][1] = StringRegExp($aArray[$nN+1],"([^\v]+)", 3)
    $nT += 1
Next
_ArrayDisplay($aArray2D, "Array 2D")

; Show all ======================
For $nN = 0 To UBound($aArray2D)-1
    _ArrayDisplay($aArray2D[$nN][1],  "Arr. "& $nN)
Next

P.S.
Функция FileReadLine С точки зрения производительности, использовать эту функцию в цикле, увеличивая параметр line на 1 при в каждом шаге цикла - является плохой идеей. Это заставляет AutoIt перечитывать файл с самого начала до указанной строки.
 
Последнее редактирование:

Webarion

Осваивающий
Сообщения
143
Репутация
24
Последний вариант вроде норм. Можно кстати метки на забивать, они же есть в массиве.
Код:
$i = 0
If $sLine = $aTagNames[$i] Then _GetNextParams($hFile, $aArr1, $aTagNames)
$i +=1

Но есть пара вопросов по поводу функции:
1. Не пойму никак зачем двигать FileSetPos? Вроде же по кольцу получается ходим в цикле. Запомнили, считали, восставовили и никогда не выйдем.
2. Зачем Return $aSearchParams ведь мы работаем через ByRef
Или так:
Код:
If $sLine = $aTagNames[0] Then _GetNextParams($hFile, $aArr1, $aTagNames)
If $sLine = $aTagNames[1] Then _GetNextParams($hFile, $aArr2, $aTagNames)

Можно также код упростить. Массив меток объявить глобально: Global $aTagNames = ["метка1", "метка2", "метка3"]
Тогда первая строка функции: Func _GetNextParams($hFile, ByRef $aSearchParams)
А запросы к функции будут такими:
Код:
If $sLine = $aTagNames[0] Then _GetNextParams($hFile, $aArr1)
If $sLine = $aTagNames[1] Then _GetNextParams($hFile, $aArr2)

По вопросам:
1. Попробуйте убрать FileSetPos и недосчитаетесь половины массивов. Вне функции, цикл нахождения меток, внутри, цикл нахождения "параметров", продолжающий внешний, который завершается дойдя до новой метки либо конца файла. А так как, в функции, найденная метка уже прочтена, при выходе из функции, в первом цикле она будет пропущена и мы потеряем целый массив. Бесконечного цикла не будет. Мы просто устанавливаем позицию на пропущенную метку.
2. Return можно убрать.

Весь код можно сделать и в одном цикле и через рекурсию, но читаться для вас это будет сложнее. А по сути, тоже самое.

Тут разные могут быть алгоритмы. Действительно ли вам необходимо читать файл построчно? Ведь распарсить его можно и быстрее, прочитав целиком. Если метки, у вас, строго определены как метка1...меткаN, то это другой код, он конечно же проще. Код может быть разным, многое зависит также от того, сами ли вы создаёте файл, либо читаете сторонний.
Сообщение автоматически объединено:

На мой взгляд так было бы быстрее, во всех отношениях
Да, быстрее, тем более, если в файле, метки строго определены базовым именем и номером. FlieRead действительно шустрее FlieReadLine.
Функция FileReadLine С точки зрения производительности, использовать эту функцию в цикле, увеличивая параметр line на 1 при в каждом шаге цикла - является плохой идеей. Это заставляет AutoIt перечитывать файл с самого начала до указанной строки.
Это касается явного указания строки FileReadLine($hFile, $i)

Тесты:
Код:
#Region создаём текстовый файл для примера

    Local $iLines = 2000 ; сколько строк создать

    Local $sPathFile = @ScriptDir & "\Test.txt"
    ConsoleWrite(@CRLF & "> Создание файла " & $sPathFile)
    Local $sTextFile
    For $i = 1 To $iLines
        $sTextFile &= Random(111111, 999999, 1) & @CRLF
    Next
    $sTextFile &= "ENDTAG"
    FileDelete($sPathFile)
    FileWrite($sPathFile, $sTextFile)
    ConsoleWrite(" - OK!" & @CRLF)
#EndRegion создаём текстовый файл для примера

; будем искать последний ENDTAG

ConsoleWrite(@CRLF & "> Тест времени парсинга файла с " & $iLines & " строк:" & @CRLF)

#Region тестируем чтение файла целиком
    ConsoleWrite("- при чтении целиком                       FileRead(path):             ")
    Local $iTimer = TimerInit()
    $sTextFile = FileRead($sPathFile)
    Local $aText = StringSplit($sTextFile, @CRLF, 1)
    For $i = 1 To $aText[0]
        If $aText[$i] = "ENDTAG" Then ExitLoop
    Next
    $iTimer = TimerDiff($iTimer)
    ConsoleWrite($iTimer & " мс" & @CRLF)
#EndRegion тестируем чтение файла целиком

#Region тестируем обычное построчное чтение
    ConsoleWrite("- при чтении построчно                     FileReadLine(handle):       ")
    Local $hFile, $sLine
    $iTimer = TimerInit()
    $hFile = FileOpen($sPathFile, 0)
    While 1
        $sLine = FileReadLine($hFile)
        If @error = -1 Then ExitLoop
        If $sLine = "ENDTAG" Then ExitLoop
    WEnd
    FileClose($hFile)
    $iTimer = TimerDiff($iTimer)
    ConsoleWrite($iTimer & " мс" & @CRLF)
#EndRegion тестируем обычное построчное чтение

#Region тестируем построчное чтение указывая Line
    ConsoleWrite("- при чтении построчно с указанием строки  FileReadLine(handle, line): ")
    Local $i = 1
    $iTimer = TimerInit()
    $hFile = FileOpen($sPathFile, 0)
    While 1
        $sLine = FileReadLine($hFile, $i)
        If @error = -1 Then ExitLoop
        If $sLine = "ENDTAG" Then ExitLoop
        $i += 1
    WEnd
    FileClose($hFile)
    $iTimer = TimerDiff($iTimer)
    ConsoleWrite($iTimer & " мс" & @CRLF)
#EndRegion тестируем построчное чтение указывая Line

FileDelete($sPathFile)
 
Последнее редактирование:

Andrey_A

Продвинутый
Сообщения
319
Репутация
68
Я бы пошёл по другому пути
Код:
$sFile='E:\Test.txt'
$sText=FileRead($sFile)
$sText=StringRegExpReplace($sText,'(?m)^(метка\d+)$','[$1]') ; обрамляем метки в квадратные скобки и работаем с текстом как с ini файлом
MsgBox(4096,"Переменная $sText","$sText =>"&$sText&"<=")
; $sText=StringRegExpReplace($sText,'\A[\r\n]+|[\r\n]+\z|(\R)(\R+)','$1') ; если надо удаляем все лишние переносы строк
$aR=StringRegExp($sText,'(?m)^\[(?:[^]\r\n]+)\][\r\n]+((?:[^[\r\n].*(?:[\r\n]+|$))*)',3) ; получаем содержимое всех секций в массив
_ArrayDisplay($aR,"Массив $aR")
For $i=0 To UBound($aR)-1
  $aR[$i]=StringSplit($aR[$i],@CRLF,1) ; содержимое каждой секции разбиваем в массив по @CRLF
Next
_ArrayDisplay($aR[0],"Массив $aR[0]")
 
Последнее редактирование:
  • Like
Реакции: Norm

Norm

Продвинутый
Сообщения
269
Репутация
70
Да, быстрее, тем более, если в файле, метки строго определены базовым именем и номером.
Так ведь ничего другого и не давалось.
FlieRead действительно шустрее FlieReadLine.
Тогда зачем писать большой код, который к тому же ещё и медленный.
Не в упрек, но этот код совершенно не годится для больших файлов :unknown:
Как его использовать если таких меток 50 или 100000 ?

Присоединяюсь к Andrey_A, который по сути повторил то, что сделано у меня
Я бы пошёл по другому пути
 

Webarion

Осваивающий
Сообщения
143
Репутация
24
Так ведь ничего другого и не давалось.

Тогда зачем писать большой код, который к тому же ещё и медленный.
Не в упрек, но этот код совершенно не годится для больших файлов :unknown:
Как его использовать если таких меток 50 или 100000 ?

Присоединяюсь к Andrey_A, который по сути повторил то, что сделано у меня
Автор вопроса неоднократно указал на FileReadLine. А вопрос в спайке со словом "на-лету" побудил меня показать именно такой вариант. Дело тут не в зачем, а в насколько это действительно необходимо человеку. Во всяком случае в ветке даны решения обоих вариантов и выбор есть.
На счёт меток согласен. Решается одной строчкой.
Ну, а лично я бы действительно прочитал файл целиком и разобрал регуляркой.
Если же файл очень большой, при FileRead, он полностью загружается в память. При чтении файла построчно, в памяти выделяется буфер небольшого размера и файл будет считываться блоками. Из блока читается строка и возвращается в переменную. Поэтому, не будет большой загрузки оперативной памяти. Таким образом, используя FileRead мы берём скоростью, но расплачиваемся памятью. В FileReadLine наоборот.
 
Последнее редактирование:
  • Like
Реакции: Norm
Автор
M

mike2003

Новичок
Сообщения
10
Репутация
0
Спасибо за советы, это все я для себя делаю, гигнаских объемов не планируется, поэтому и тормоза наверное меня не будут беспокоить. Для меня важно сейчас понять и отладить работу, а с регулярными выражениями у меня не никак складывается. Захочу обновить и зависну. Я даже сейчас не понимаю, что там происходит и что мне надо под себя подгонять.
 
Верх