PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » Программирование на PureBasic » Преобразовать список в масив


Преобразовать список в масив

Сообщений 1 страница 8 из 8

1

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

Код:
EnableExplicit

Define String$ = "проверка, 1 что 1 поля 1 захватываются 1 в 1 массив"
Define count, i

Procedure.s Fields(String$, Field$)
	Protected Pos, TmpPos, Res$, Length
	Length = Len(Field$)
	String$ + Field$
	Pos = 1 - Length
	Repeat
    TmpPos = Pos + Length
    Pos = FindString(String$, Field$, TmpPos, #PB_String_NoCase)
    If Pos
    	Res$ + "|" + Mid(String$, TmpPos, Pos - TmpPos) + "|" + #CRLF$
    Else
    	ProcedureReturn Res$
    EndIf
	ForEver
EndProcedure

Procedure aFields(String$, Field$, Array aRes.s(1))
	Protected Pos, TmpPos, Length, i
	Length = Len(Field$)
	String$ + Field$
	Pos = 1 - Length
	i = 0
	Repeat
    TmpPos = Pos + Length
    Pos = FindString(String$, Field$, TmpPos, #PB_String_NoCase)
    If Pos
    	i + 1
    	ReDim aRes(i)
    	aRes(i) = Mid(String$, TmpPos, Pos - TmpPos )
    Else
    	ProcedureReturn i
    EndIf
	ForEver
EndProcedure

Procedure lFields(String$, Field$, List Res.s())
	Protected Pos, TmpPos, i, Length
	Length = Len(Field$)
	String$ + Field$
	Pos = 1 - Length
	Repeat
    TmpPos = Pos + Length
    Pos = FindString(String$, Field$, TmpPos, #PB_String_NoCase)
    If Pos
    	AddElement(Res())
    	Res() = Mid(String$, TmpPos, Pos - TmpPos )
    Else
    	ProcedureReturn ListSize(Res())
    EndIf
	ForEver
EndProcedure

Debug "=== Array ==="
Dim aRes.s(0)
count = aFields(String$, " 1 ", aRes())
For i = 1 To count
	Debug aRes(i)
Next

Debug #CRLF$ + "=== List ==="
NewList Res.s()
count = lFields(String$, " 1 ", Res())
ForEach Res()
	Debug Res()
Next

MessageRequester("", Fields(String$, " 1 "))

Отредактировано AZJIO (18.12.2020 00:06:00)

0

2

Сделал тест, разница к сожалению небольшая 3% для 20 тыс. элементов.

Код:
EnableExplicit

Define String$, Path.s, Res.s
Define count, i, x, StartTime

Path = "/media/.../file.fb2"
ReadFile(0, Path)
String$ = ReadString(0, #PB_UTF8 | #PB_File_IgnoreEOL)
CloseFile(0)
; String$ = "проверка, что поля захватываются в массив"


Procedure aFields(String$, Field$, Array aRes.s(1))
	Protected Pos, TmpPos, Length, i
	Length = Len(Field$)
	String$ + Field$
	Pos = 1 - Length
	i = 0
	Repeat
    TmpPos = Pos + Length
    Pos = FindString(String$, Field$, TmpPos, #PB_String_NoCase)
    If Pos
    	i + 1
    	ReDim aRes(i)
    	aRes(i) = Mid(String$, TmpPos, Pos - TmpPos )
    Else
    	ProcedureReturn i
    EndIf
	ForEver
EndProcedure

Procedure lFields(String$, Field$, List Res.s())
	Protected Pos, TmpPos, i, Length
	Length = Len(Field$)
	String$ + Field$
	Pos = 1 - Length
	Repeat
    TmpPos = Pos + Length
    Pos = FindString(String$, Field$, TmpPos, #PB_String_NoCase)
    If Pos
    	AddElement(Res())
    	Res() = Mid(String$, TmpPos, Pos - TmpPos )
    Else
    	ProcedureReturn ListSize(Res())
    EndIf
	ForEver
EndProcedure

Procedure StringSplit(Array a$(1), s$, delimeter$)
  Protected count, i
  count = CountString(s$,delimeter$) + 1
  Dim a$(count)
  For i = 1 To count
    a$(i - 1) = StringField(s$,i,delimeter$)
  Next
  ProcedureReturn count ;return count of substrings
EndProcedure

x=2
If x = 1

Debug "=== aFields ==="
StartTime=ElapsedMilliseconds()
Dim aRes.s(0)
count = aFields(String$, " ", aRes())
; For i = 1 To count
; 	Debug aRes(i)
; Next
Res = "Прошло времени между метками " + Str(ElapsedMilliseconds()-StartTime) + " мсек, элементов " + count
Debug Res

ElseIf x = 2

Debug "=== lFields ==="
StartTime=ElapsedMilliseconds()
NewList Res.s()
count = lFields(String$, " ", Res())
Res = "Прошло времени между метками " + Str(ElapsedMilliseconds()-StartTime) + " мсек, элементов " + count
Debug Res
; ForEach Res()
; 	Debug Res()
; Next

Else

Debug "=== StringSplit ==="
StartTime=ElapsedMilliseconds()
Dim aRes.s(0)
count = StringSplit(aRes(), String$, " ")
; For i = 1 To count
; 	Debug aRes(i)
; Next
Res = "Прошло времени между метками " + Str(ElapsedMilliseconds()-StartTime) + " мсек, элементов " + count
Debug Res

EndIf

Вместо Mid использовал PeekS, уже 30%

Код:
Res() = PeekS(@String$ + TmpPos*2 - 2, Pos - TmpPos, #PB_Unicode)

Отредактировано AZJIO (18.12.2020 00:06:17)

0

3

AZJIO написал(а):

разница к сожалению небольшая

Почему к сожалению? К счастью, что ни чего колхозить не надо, всё работает "из коробки"  8-)

0

4

ВиниПур
на AutoIt3 аналогичная StringSplit работает мгновенно - 13 мсек (против 3000 мсек, в 200 раз).

Чтобы не быть голословным можете проверить:

Код:
$sText = FileRead(@ScriptDir & '\file.fb2')
$timer = TimerInit()
$aText = StringSplit($sText, " ")
MsgBox(0, "Время выполнения" & $aText[0], 'Время : ' & Round(TimerDiff($timer), 2) & ' мсек')

Отредактировано AZJIO (19.12.2020 05:12:56)

0

5

AZJIO написал(а):

на AutoIt3 аналогичная StringSplit работает мгновенно - 13 мсек

На Пурике тоже много всяких ускоренных алгоритмов написано. Вот например, у меня на разбор 20000 слов уходит 0 мсек

Код:
DeclareModule FastSplit
   
    Declare.i Fast_Split_To_Array(*pio_to_split, pi_sep.s, Array result.s(1))
   
EndDeclareModule

Module FastSplit
   
    ; *pio_to_split :   Unicode string pointer assumed => 2 bytes size par character
    ; pi_sep        :   Unicode separator assumed, can have several chars (typically #CRLF$)
    ; result        :   will be updated
    ; RETURN        :   result array elements count
    Procedure.i Fast_Split_To_Array(*pio_to_split, pi_sep.s, Array result.s(1))
       
        ; Non printable Unicode char
        #REPLACE_CHAR = $FFFF
       
        ; Macro used here to avoid a costly asm CALL
        Macro Macro_Asm_Set_Array_String_Pointer
            ! mov eax, 4                            ; 4 bytes size assumed for a 32 bits (x86) pointer
            ! mov [p.p_pedx_backup], edx            ; "edx" register backup (can't use "push/pop edx" because PB local vars are "esp" indexed)
            ! mul dword [p.v_current_element]       ; Multiply by current element index and put the result in "eax"/ Beware that "mul" use "edx" register, so the previous backup
            ! mov edx, [p.p_pedx_backup]            ; Get "edx" register value back
            ! add eax, [p.p_parray_string_pointers] ; Add result to get the start address in the original string
            ! mov [eax], edx                        ; Put computed address in the current array index
        EndMacro
       
        ; Exit if nothing to split
        If Len(PeekS(*pio_to_split)) = 0
            ProcedureReturn 0
        EndIf
       
        ; One element array if no separator
        If Len(pi_sep) = 0
            ReDim result(0)
            result(0) = PeekS(*pio_to_split)
            ProcedureReturn 1
        EndIf
       
        ; Local définition of separator (why ? just to show that "pi_sep" should be readonly, no performance hit here)
        Define.s local_sep = pi_sep
       
        ; More than one character in separator => one time performance hit to replace all separator chars by one (#REPLACE_CHAR)
        ; but "ReplaceString" is very well optimized (thanks Fred :) so performance hit is limited
        If Len(local_sep) > 1
            PokeS(*pio_to_split, ReplaceString(PeekS(*pio_to_split), local_sep, Chr(#REPLACE_CHAR)))
            local_sep = Chr(#REPLACE_CHAR)
        EndIf
       
        ; Get the size of zero based result array ("CountString" is very optimized too :)
        Define.i sep_count = CountString(PeekS(*pio_to_split), local_sep)
       
        ; Prepare the result array
        ReDim result(sep_count)
       
        ; No separators found ? Return the original string in an one-element result array
        If sep_count = 0
            result(0) = PeekS(*pio_to_split)
            ProcedureReturn 1
        EndIf
       
        ; Definitions
        Define.i sep = Asc(local_sep) ; Two chars in Unicode, which is assumed here
        Define.i current_element = 0  ; Array current element processing
       
        ; Trick #1 : get the array REAL address
       
        ; In fact, PB here returns the array first element address (but in the MemoryViewer, "@result()" show the real array address)...
        Define *parray_addr = @result()
        Define *parray_string_pointers = PeekI(@*parray_addr) ; ...have to go up to find the real array address, which contains the vTable of strings in array
        Define *pedx_backup                                   ; Our "edx" register backup (see explanations above in the macro)
       
        ! mov ecx, [p.p_pio_to_split]                   ; Load string start address here...
        ! mov edx, [p.p_pio_to_split]                   ; ...and here
       
        Scan:                                           ; Scan loop
        ! mov ax, [ecx]                                 ; Get current char (Unicode => 2 bytes => ax is ok)
        ! cmp ax, 0                                     ; String end ?
        ! jz fastsplit.ll_fast_split_to_array_endscan   ; Exit to EndScan:
       
        ! cmp ax, [p.v_sep]                             ; Separator found ?
        ! jz fastsplit.ll_fast_split_to_array_sepfound  ; Process to SepFound:
       
        NextChar:                                       ; Point to next char
        ! add ecx, 2                                    ; Unicode => 2 bytes increment
        ! jmp fastsplit.ll_fast_split_to_array_scan     ; Loop to Scan:
       
        ; Trick #2 : replace array original pointer by pointer in original string => no string memory allocation
       
        SepFound:                                       ; Separator found
        ! mov word [ecx], 0                             ; Replace separator by 2 zero-bytes (=> string end)
        Macro_Asm_Set_Array_String_Pointer              ; Our killer macro :)
        ! mov edx, ecx                                  ; Point to...
        ! add edx, 2                                    ; ...next char
        ! inc dword [p.v_current_element]               ; Increment array current element
        ! jmp fastsplit.ll_fast_split_to_array_nextchar ; Read next char to NextChar:
       
        EndScan:
        Macro_Asm_Set_Array_String_Pointer              ; Our killer Macro again :) for the last value
       
        ProcedureReturn sep_count + 1                   ; Array elements count (PB ArraySize() return the array upper bound, not necessarily the elements count)
       
    EndProcedure
   
EndModule

If #PB_Compiler_IsMainFile

  If ReadFile(0, "D:\Text20000.txt")
    Debug "Ok"
       While Eof(0) = 0
         txt$  + ReadString(0, #PB_UTF8)+ #CRLF$  ; прочитал 20000 слов в переменную
       Wend
 	 CloseFile(0)
 	EndIf
 	
 	  	
 	; Собственно само выполнение программы тут:
 Define.i elements_count, i
 Dim a.s(0)
 StartTime=ElapsedMilliseconds()
 elements_count = FastSplit::Fast_Split_To_Array(@txt$, " ", a())
 Res.s = "Прошло времени между метками " + Str(ElapsedMilliseconds()-StartTime) + " мсек, элементов " +  elements_count
 MessageRequester ("Ok! ",Res)
 
   
EndIf 

0

6

Вот без ассемблера, работает с 20000 слов всего 2 мсек ( с отключенным режимом Debug), тоже вполне достойный результат.

Код:
Procedure Split(String.s, Array StringArray.s(1), Separator.s = " ")
 
  Protected S.String, *S.Integer = @S
  Protected.i asize, i, p, slen
  asize = CountString(String, Separator)
  slen = Len(Separator)
  ReDim StringArray(asize)
 
  *S\i = @String
  While i < asize
    p = FindString(S\s, Separator)
    StringArray(i) = PeekS(*S\i, p - 1)
    *S\i + (p + slen - 1) << #PB_Compiler_Unicode
    i + 1
  Wend
  StringArray(i) = S\s
  *S\i = 0
 
EndProcedure

; test
If ReadFile(0, "D:\Text20000W.txt")
    Debug "Ok"
       While Eof(0) = 0
         txt$  + ReadString(0, #PB_UTF8)+ #CRLF$  ; прочитал 20000 слов
       Wend
 	 CloseFile(0)
 	EndIf
S.s =  txt$

Dim MyStrings.s(0)
StartTime=ElapsedMilliseconds()
Split(S, MyStrings())
Res.s = "Прошло времени между метками " + Str(ElapsedMilliseconds()-StartTime) + " мсек, элементов " +  ArraySize(MyStrings())
For i = 0 To 10
  Debug MyStrings(i)
Next
 MessageRequester ("Ok! ",Res)

0

7

Было бы правильней сразу давать ссылку на источник

0

8

AZJIO написал(а):

Было бы правильней сразу давать ссылку на источник

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

0


Вы здесь » PureBasic - форум » Программирование на PureBasic » Преобразовать список в масив