mieki256's diary



2024/02/15(木) [n年前の日記]

#1 [basic] FreeBASICでsplit()を使いたい。その2

_昨日、 FreeBASIC で split() を ―― 文字列を区切り文字で分割して配列にする関数の実装を試してみたけれど。ダブルクオーティション(")が入ってくる場合に望んだ形にならなかったので、そのあたりをどうにかできないかと試していた。

環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。

ソース :

一応処理は書けたような気がする。もはや split() の動作ではなくなっているけれど…。

実処理は以下。

_splitdq.bi
' License: CC0 / Public Domain

#ifndef __SPLITDQ__
#define __SPLITDQ__

Sub Splitdq(byref text As String, byref delim As String, result() as String)
    If text = "" Then Return
        
    Dim As ubyte ptr cp = strptr(text)
    Dim As ubyte ptr dp = strptr(delim)
    Dim As Integer slen = Len(text)
    Dim As Integer pi = 0
    Dim As Integer i = 0
    Dim As Boolean dqfg = False
    Dim As String s = ""
    
    Do
        If dqfg Then
            ' check double quotation pair
            If (*cp = 34) Then dqfg = False
        ElseIf (*cp = 34) Then
            ' found double quotation
            dqfg = True
        ElseIf (*cp = *dp) Then
            ' found delimiter
            s = Mid(text, pi + 1, (i - pi))
            pi = i + 1
        End If

        cp += 1
        i += 1

        If i >= slen Then
            s = Mid(text, pi + 1, (i - pi))
            pi = i + 1
        End If
        
        If s <> "" Then
            ReDim preserve result(UBound(result) + 1)
            result(UBound(result)) = s
            s = ""
        End If
    Loop While (i < slen)
End Sub

#endif


テストサンプルは以下。

_test_splitdq.bas
#include "splitdq.bi"

Dim As String text
'text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitdq(text, " ", words())

Print text
For i As Integer = 0 To UBound(words)
    Print "[" & words(i) & "]"
Next i
Print "    ...success."

fbc test_splitdq.bas でコンパイル。実行結果は以下。

> test_splitdq.exe
info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2
[info]
[face="DejaVu Sans"]
[size=24]
[bold=1]
[italic=0]
[charset="ANSI"]
[unicode=0]
[stretchH=100]
[smooth=1]
[aa=4]
[padding=0,0,0,0]
[spacing=1,1]
[outline=2]
    ...success.

ダブルクオーティションで挟まれた部分については区切り文字を無視するようにできた。

ただ、バグがありそうな気がする…。こういう文字列を渡すとおかしくなる、という場面がありそうな…。

処理の仕方も無駄がありそう。少なくとも処理速度は期待できない予感。

でもまあ、今回やりたいことはできそうだから、これでもいいか…。

split()の実装が気になってきた :

一般的に split() ってどんな感じに実装されたりするのか気になってググってみた。

以下の事例を見て目ウロコ。元文字列の最後に区切り文字を追加してから処理を始めることで、ループがスッキリしてる…。その手があったか…。

_Split the string into substrings using delimiter - GeeksforGeeks


FreeBASIC版を書いてみた。ダブルクオーティションに対応させようとして、記述が長くなってしまったけれど…。

_splitstringa.bi
#ifndef __SPLITSTRINGA__
#define __SPLITSTRINGA__

Sub splitStringA(ByVal text As String, ByVal delim As String, result() as String, ByVal chk_dbqt As Boolean = False)
    If text = "" Then Return
    Dim As String src = text & delim
    Dim As String word = ""
    Dim As Boolean founded_dbqt = False

    For i As Integer = 0 To Len(src) - 1
        If founded_dbqt Then
            word &= Chr(src[i])
            If src[i] = 34 Then founded_dbqt = False  ' found double quote
        Else
            If src[i] <> delim[0] Then
                ' not delimiter
                word &= Chr(src[i])
                If chk_dbqt And src[i] = 34 Then founded_dbqt = True  ' found double quote
            Else
                ' found delimiter
                If word <> "" Then
                    ' word is not empty. save word to array
                    ReDim preserve result(UBound(result) + 1)
                    result(UBound(result)) = word
                    word = ""
                End If
            End If
        End If
    Next i
    If word <> "" Then
        ' word is not empty. save word to array
        ReDim preserve result(UBound(result) + 1)
        result(UBound(result)) = word
    End If
End Sub

#endif


使用サンプルは以下。fbc test_splitstringa.bas でコンパイル。

_test_splitstringa.bas
#include "splitstringa.bi"

Dim As String text
text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitStringA(text, " ", words())
'splitStringA(text, " ", words(), True)  ' check double quote

Print "[" & text & "]"
For i As Integer = 0 To UBound(words)
    Print i & ": [" & words(i) & "]"
Next i
Print "end."


test_splitstringa.exe を実行。たしかに分割できている。

> test_splitstringa.exe
[char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15]
0: [char]
1: [id=32]
2: [x=250]
3: [y=22]
4: [width=5]
5: [height=5]
6: [xoffset=-2]
7: [yoffset=21]
8: [xadvance=7]
9: [page=0]
10: [chnl=15]
end.


それとは別の実装事例。FreeBASICのWikiを眺めてたら、そもそも split() の実装サンプルが紹介されてた。まさかそんなページで紹介されていたとは…。

_Passing Arrays to Procedures - FreeBASIC Wiki Manual | FBWiki

内容的には、instr() で区切り文字を探して位置を求めて、頭からそこまでの文字列を配列に記録したら、その文字列分を元文字列から削除して処理を続けていた。サブルーチンに引数として元文字列を渡す際、値渡しで渡してるから、元文字列を破壊しても大丈夫ということなのだろう…。もしかすると strtok() を使った事例と考え方は似ているのかもしれない。

これも手元で動作確認してみた。

_splitstringb.bi
#ifndef __SPLITSTRINGB__
#define __SPLITSTRINGB__

Sub splitStringB(ByVal src As String, ByVal delim As String, result(Any) As String)
    Do
        Dim As Integer i = InStr(1, src, Chr(delim[0])) ' search delimiter
        ReDim Preserve result(UBound(result) + 1)
        If i = 0 Then
            ' not found delimiter. exit loop
            result(UBound(result)) = src
            Exit Do
        End If
        result(UBound(result)) = Left(src, i - 1)  ' save word to array
        src = Mid(src, i + 1)  ' delete word
    Loop
End Sub

#endif


使用サンプルは以下。fbc test_splitstringb.bas でコンパイル。

_test_splitstringb.bas
#include "splitstringb.bi"

Dim As String text
text = "char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15"
'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2"

Dim words() As String
splitStringB(text, " ", words())

Print "[" & text & "]"
For i As Integer = 0 To UBound(words)
    Print i & ": [" & words(i) & "]"
Next i
Print "end."


ただ、この版は、区切り文字が連続して並んでる際、それぞれを配列変数に入れてしまう。

> test_splitstringb.exe
[char id=32   x=250   y=22    width=5     height=5     xoffset=-2    yoffset=21    xadvance=7     page=0  chnl=15]
0: [char]
1: [id=32]
2: []
3: []
4: [x=250]
5: []
6: []
...
34: [page=0]
35: []
36: [chnl=15]
end.

もしかすると文字列を切り出した後、文字列が空かどうかを調べて、空じゃなければ配列に格納するようにすればいいのだろうか…?

マルチバイト文字列対応は大変そう :

1文字=1byteの文字列なら処理が書けたので、せっかくだからマルチバイト文字列にも対応させられないものかと思い立ったのだけど。String を、マルチバイト文字列を格納する WString に書き換えれば済むかなと思ったらコンパイルすら通らず。

WString や ZString は、扱いが面倒らしい…。

_FreeBASICのワイド文字列が使いにくい件 - 飴屋ぷろじぇくと
_FreeBASIC Compiler - 飴屋ぷろじぇくと
_Problem passing and copying fixed length WSTRING array - freebasic.net

あらかじめ領域を固定で確保しておかないといけないとか、サブルーチンや関数に WString の配列変数を渡せないとか…。これはなかなか大変…。

区切り"文字列"にすると大変 :

区切り"文字"で分割する分にはまだこうして書けるけど、区切り"文字列"で分割できるように対応しようとするとちょっと大変そう。文字列検索/文字列探索アルゴリズムの話になって、KMP法とかBM法とかが出てくる…。

_文字列探索アルゴリズムとは?KMP法やBM法について解説
_文字列検索アルゴリズムについて #アルゴリズム - Qiita

以上です。

過去ログ表示

Prev - 2024/02 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project